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:
| Interface | Extends | Describes |
|---|---|---|
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:
| Method | Job |
|---|---|
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.
| Method | Job |
|---|---|
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 type | Java 21 relationship |
|---|---|
List | Extends SequencedCollection |
Deque | Extends SequencedCollection |
LinkedHashSet | Implements SequencedSet |
SortedSet | Extends SequencedSet |
LinkedHashMap | Implements SequencedMap |
SortedMap | Extends 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:
| Question | Honest 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()andgetLast()when the code means “the ends.” - Accept
SequencedCollection,SequencedSet, orSequencedMapwhen 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
HashSetiteration 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.