You know the collection has an encounter order. Getting its first or last element should be obvious — yet the code depends on whether the value is a List, a Deque, a LinkedHashSet, or a sorted collection.

Before Java 21, those types shared no interface that described their common order. Generic code either accepted a concrete type, used an iterator, or copied elements just to walk them backward.

JEP 431 fixes that gap with sequenced collections: interfaces for collections and maps whose elements have a defined order from first to last. The feature is final in Java 21 — no preview flag required.

The problem: order without a shared API

A List has indexes, a Deque has two ends, and a LinkedHashSet preserves insertion order. They all have a first and last element, but older APIs made you ask each one differently.

List<String> list = List.of("alpha", "beta", "gamma");
String listFirst = list.get(0);
String listLast = list.get(list.size() - 1);

Deque<String> deque = new ArrayDeque<>(list);
String dequeFirst = deque.getFirst();
String dequeLast = deque.getLast();

LinkedHashSet<String> set = new LinkedHashSet<>(list);
String setFirst = set.iterator().next();

The last element of that set required another loop, a stream reduction, or a copy. A method that only needed “an ordered collection” could not express that requirement in its parameter type.

Java 21 gives that requirement a name:

static <E> void printEnds(SequencedCollection<E> values) {
    System.out.println("first = " + values.getFirst());
    System.out.println("last  = " + values.getLast());
}

Now the same method accepts a list, deque, ordered set, or sorted set without discarding the fact that order matters.

The three new interfaces

JEP 431 adds three interfaces in java.util:

InterfaceExtendsDescribes
SequencedCollection<E>Collection<E>Elements with a defined first-to-last encounter order
SequencedSet<E>Set<E>, SequencedCollection<E>Unique elements with that encounter order
SequencedMap<K, V>Map<K, V>Key-value mappings with a defined encounter order

The hierarchy makes order part of the type contract. SequencedCollection means the ends are meaningful, not merely that one implementation happens to iterate predictably today.

SequencedCollection: work from either end

SequencedCollection provides five operations:

MethodJob
getFirst()Read the first element
getLast()Read the last element
addFirst(E)Add an element at the front
addLast(E)Add an element at the back
reversed()Return a reverse-ordered view

The read methods make end access uniform:

SequencedCollection<String> tasks =
        new ArrayList<>(List.of("compile", "test", "deploy"));

System.out.println(tasks.getFirst()); // compile
System.out.println(tasks.getLast());  // deploy

Calling getFirst() or getLast() on an empty collection throws NoSuchElementException. Check isEmpty() when an empty input is expected.

The add methods let mutable implementations expose both ends through the same interface:

tasks.addFirst("format");
tasks.addLast("notify");

System.out.println(tasks);
// [format, compile, test, deploy, notify]

Support still depends on the collection. An unmodifiable list rejects additions, and a sorted collection cannot accept a caller-chosen front or back without violating its sort order.

Note: The interface supplies uniform operations, not uniform mutability. addFirst may throw UnsupportedOperationException.

reversed() returns a view, not a copy

The most important mental model is simple: reversed() is not a copy. It returns a view that presents the same collection from the opposite direction.

List<String> names =
        new ArrayList<>(List.of("Ada", "Grace", "Linus"));

List<String> reversed = names.reversed();

System.out.println(reversed); // [Linus, Grace, Ada]

Changes through the original collection appear in the view:

names.addLast("James");

System.out.println(names);    // [Ada, Grace, Linus, James]
System.out.println(reversed); // [James, Linus, Grace, Ada]

For a modifiable collection, changes through the reversed view affect the original too. The ends swap: adding first through the reversed view adds last to the original.

reversed.addFirst("Barbara");

System.out.println(names);    // [Ada, Grace, Linus, James, Barbara]
System.out.println(reversed); // [Barbara, James, Linus, Grace, Ada]

That behavior makes reverse traversal cheap and composable. It also means the view is the wrong tool when you need an independent snapshot.

Create a copy explicitly when later mutations must not leak across:

List<String> snapshot = List.copyOf(names.reversed());

names.addLast("Margaret");

System.out.println(snapshot);
// [Barbara, James, Linus, Grace, Ada]

The reversed view has the same mutability restrictions as its backing collection. If the original is unmodifiable, the reversed view is unmodifiable.

Lists and deques now share the contract

In Java 21, List and Deque both extend SequencedCollection. Existing code gains a common vocabulary without changing its implementation.

static <E> List<E> ends(SequencedCollection<E> source) {
    if (source.isEmpty()) {
        return List.of();
    }
    if (source.size() == 1) {
        return List.of(source.getFirst());
    }
    return List.of(source.getFirst(), source.getLast());
}

System.out.println(ends(new ArrayList<>(List.of(1, 2, 3))));
System.out.println(ends(new ArrayDeque<>(List.of(1, 2, 3))));

List also receives the end operations directly, so get(0) and get(size() - 1) no longer have to obscure intent:

List<String> releases =
        new ArrayList<>(List.of("21", "22", "23"));

String oldest = releases.getFirst();
String newest = releases.getLast();

Use indexes when the position itself matters. Use getFirst() and getLast() when the code is about the ends.

SequencedSet: uniqueness with meaningful ends

SequencedSet combines uniqueness with an encounter order. Its reversed() method returns a SequencedSet, preserving the stronger set contract.

SequencedSet<String> modules = new LinkedHashSet<>();
modules.add("api");
modules.add("domain");
modules.add("storage");

System.out.println(modules.getFirst()); // api
System.out.println(modules.getLast());  // storage
System.out.println(modules.reversed()); // [storage, domain, api]

LinkedHashSet now implements SequencedSet. On Java 21 it can deliberately reposition an existing element by adding it at an end.

modules.addFirst("storage");

System.out.println(modules);
// [storage, api, domain]

Because a set contains each element once, addFirst(existing) and addLast(existing) move that element to the requested end rather than creating a duplicate.

HashSet is not sequenced

HashSet does not define an encounter order, so HashSet is not sequenced. Its current iteration order is an implementation detail and must not be treated as first-to-last ordering.

This method accepts a LinkedHashSet but correctly rejects a plain HashSet at compile time:

static <E> E newest(SequencedSet<E> values) {
    return values.getLast();
}

newest(new LinkedHashSet<>(List.of("a", "b"))); // b
// newest(new HashSet<>(List.of("a", "b")));    // does not compile

If order is part of the requirement, choose LinkedHashSet or a sorted set and expose SequencedSet in the API.

SortedSet joins the hierarchy

SortedSet now extends SequencedSet, because its comparator or natural ordering already defines the sequence. Its first and last elements are the least and greatest according to that ordering.

SortedSet<Integer> scores = new TreeSet<>(List.of(40, 10, 30, 20));

System.out.println(scores.getFirst()); // 10
System.out.println(scores.getLast());  // 40
System.out.println(scores.reversed()); // [40, 30, 20, 10]

A sorted set controls element position, so addFirst() and addLast() throw UnsupportedOperationException. Use add() and let the comparator place the element.

scores.add(25);       // supported; ordering decides its position
// scores.addFirst(5); // UnsupportedOperationException

This is why accepting SequencedCollection does not promise that all five operations will be supported. The type promises order; the implementation determines mutation rules.

SequencedMap: ordered mappings from both ends

Maps do not extend Collection, so JEP 431 introduces the separate SequencedMap interface. It provides ordered entry operations and sequenced views of keys, values, and entries.

MethodJob
firstEntry() / lastEntry()Read an end entry
pollFirstEntry() / pollLastEntry()Remove and return an end entry
putFirst(K, V) / putLast(K, V)Insert or reposition a mapping at an end
reversed()Return a reverse-ordered map view
sequencedKeySet()Return the keys as a SequencedSet
sequencedValues()Return the values as a SequencedCollection
sequencedEntrySet()Return entries as a SequencedSet

LinkedHashMap now implements SequencedMap, giving insertion-ordered maps direct access to their ends:

SequencedMap<String, Integer> queue = new LinkedHashMap<>();
queue.put("low", 1);
queue.put("normal", 2);
queue.put("urgent", 3);

System.out.println(queue.firstEntry()); // low=1
System.out.println(queue.lastEntry());  // urgent=3

Unlike collection end reads, firstEntry() and lastEntry() return null when the map is empty. The returned entries are snapshots and do not support setValue().

You can place or reposition entries explicitly:

queue.putFirst("urgent", 3);
queue.putLast("low", 1);

System.out.println(queue);
// {urgent=3, normal=2, low=1}

The reversed map is a live view, just like the collection view:

SequencedMap<String, Integer> reverseQueue = queue.reversed();
reverseQueue.putFirst("later", 0);

System.out.println(queue);
// {urgent=3, normal=2, low=1, later=0}

putFirst() on the reversed view corresponds to putLast() on the original map.

What Java 21 retrofitted

The change fits existing collection types into the new hierarchy:

Existing typeJava 21 relationship
ListExtends SequencedCollection
DequeExtends SequencedCollection
LinkedHashSetImplements SequencedSet
SortedSetExtends SequencedSet
LinkedHashMapImplements SequencedMap
SortedMapExtends SequencedMap

This retrofit is source-compatible for normal usage. Existing implementations inherit or provide the new methods, while generic APIs can now request sequence semantics directly. SortedMap follows the same rule as SortedSet: key order is meaningful, but caller-selected end insertion is unsupported.

Interview lens

Interviewers want the name, which types opted in, and the two traps. They do not want you to recite the interface hierarchy.

What to draw. A list (or LinkedHashSet) with a first and a last. An arrow labeled reversed() that still points at the same storage. A HashSet off to the side with no first/last.

Typical questions:

QuestionHonest answer
What did JEP 431 add?Uniform first / last / reversed on types that already had an encounter order.
Is HashSet sequenced?No. Iteration order is not a sequence. Use LinkedHashSet.
Is reversed() a copy?No. It is a live view. Mutate one, the other sees it.
Can you addFirst on a TreeSet?No. Sorted types reject caller-selected end insertion. Order is the comparator.
LinkedHashMap vs HashMap?Only LinkedHashMap is a SequencedMap.

Wrong answer: “Every collection has a first and a last after Java 21.” Only sequenced types. HashSet and HashMap did not join.

Cheat sheet

Keep these contracts in mind when moving Java 21 code to the sequenced APIs:

SequencedCollection:
  getFirst(), getLast(), addFirst(), addLast(), reversed()

SequencedSet:
  SequencedCollection + uniqueness

SequencedMap:
  firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry()
  putFirst(), putLast(), reversed()
  sequencedKeySet(), sequencedValues(), sequencedEntrySet()

reversed() = live reverse-ordered view, not a copy
HashSet = not sequenced
Sorted collections = sequenced, but end insertion is unsupported
Java 21 / JEP 431 = final feature, no preview flag

Do:

  • Use getFirst() and getLast() when the code means “the ends.”
  • Accept SequencedCollection, SequencedSet, or SequencedMap when order is part of the contract.
  • Use reversed() for cheap reverse traversal and intentional live views.
  • Copy a reversed view explicitly when you need an independent snapshot.

Don’t:

  • Treat reversed() as a copy.
  • Assume every sequenced collection supports end insertion.
  • Treat HashSet iteration order as a sequence.
  • Use a sequenced parameter when the algorithm does not depend on order.

Wrap-up

Sequenced collections give Java’s ordered containers the vocabulary they were missing: first, last, front, back, and reverse. Lists, deques, insertion-ordered sets and maps, and sorted collections can now meet behind interfaces that preserve their shared ordering guarantee.

The two caveats matter most: HashSet has no defined sequence, and reversed() stays connected to its backing collection. Keep those contracts visible, and JEP 431 removes ceremony without hiding behavior.

On Java 21+, start by replacing index arithmetic and iterator tricks with end operations. Then look for APIs that accept concrete ordered types only because Java previously had no better abstraction — SequencedCollection, SequencedSet, and SequencedMap may now say exactly what those methods mean.

This post sits in the Java Collections series as the Java 21 vocabulary for ordered containers. The rest of the framework — contracts, ArrayList / HashMap defaults, concurrent types — is that map. When you are choosing a type by nulls, order, and threads, skip to the decision guide.

Next optional step in the series Pick a JDK type from the hot operation, not from the longest class name. Pick the Collection: Nulls, Order, Threads, and the Hot Operation