Appearance
Java 21 Sequenced Collections: SequencedCollection, SequencedSet, and SequencedMap
Java 21 introduced three brand new interfaces into the Java Collections Framework: SequencedCollection, SequencedSet, and SequencedMap. These additions solve a very real and longstanding inconsistency problem that developers have lived with for years. To truly appreciate why these interfaces matter, you need to understand the pain they eliminate. So let us start there.
The Problem Before Java 21
Imagine you have a list of items and you want to fetch the first element. You write list.get(0). Simple. Now you want the last element. You write list.get(list.size() - 1). A bit clunky, but fine.
Now you switch to a Deque because your use case demands it. To get the first element you call deque.getFirst(). To get the last element you call deque.getLast(). Completely different methods from what you used with a list.
Now you switch to a LinkedHashSet because you need ordered unique elements. How do you get the first element now? There is no getFirst() method. There is no get(0) method either. You have to write something like this:
java
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("apple");
set.add("banana");
set.add("cherry");
// Getting the first element - the old painful way
String first = set.iterator().next();
// Getting the last element - even more painful
String last = null;
for (String s : set) {
last = s;
}You have to iterate through the entire collection just to grab the last element. That is deeply unsatisfying and error prone.
What about TreeMap or LinkedHashMap? Getting the first or last entry requires yet another approach. For TreeMap you can use firstKey() or lastKey(). For LinkedHashMap you again resort to iteration.
Let us put this inconsistency in a table so you can see exactly how bad it was:
| Collection Type | Get First | Get Last | Add First | Add Last | Reverse |
|---|---|---|---|---|---|
| List | get(0) | get(size - 1) | add(0, e) | add(e) | Collections.reverse() |
| Deque | getFirst() | getLast() | addFirst(e) | addLast(e) | No built in way |
| LinkedHashSet | iterator().next() | manual loop | not supported simply | add(e) | create a new reversed structure |
| SortedSet | first() | last() | not meaningful | not meaningful | descendingSet() |
| LinkedHashMap | iterate to get entry | iterate to get entry | complex | complex | no built in way |
Every single collection type does things slightly differently. If you want to write a utility method that works on any ordered collection, you cannot. There is no common interface that guarantees you can access the first or last element in a uniform way. This is the gap that Java 21 closes.
What Makes a Collection "Sequenced"?
Before you can understand the new interfaces, you need to understand what qualifies a collection to be called sequenced. The transcript explains this with three key conditions.
Condition 1: Predictable Iteration Order
A sequenced collection must have a predictable, well defined order. There are two kinds of ordering that qualify:
- Insertion order: the collection remembers the order in which you added elements. A
Listand aLinkedHashSetboth maintain insertion order. - Sorted order: the collection keeps elements in a sorted sequence, such as ascending alphabetical or numerical order. A
TreeSetorTreeMapworks this way.
The keyword is predictable. You must be able to say with certainty which element is first and which is last.
Condition 2: First and Last Are Well Defined
Because the order is predictable, you can meaningfully talk about a "first" element and a "last" element. This is required for getFirst(), getLast(), addFirst(), addLast(), removeFirst(), and removeLast() to make sense.
Condition 3: Reversibility
If you know what first and last mean, you can reverse the sequence. A reversed view of the collection should be possible.
Now look at which collections do NOT qualify:
QueueandPriorityQueue: APriorityQueuedetermines ordering based on priority, not insertion order or a simple sorted sequence. You cannot meaningfully define "add at first" because where an element sits depends entirely on its priority weight. SoQueueandPriorityQueueare excluded.HashSet: AHashSetoffers no predictable ordering at all. Elements could be stored anywhere based on their hash code. You cannot define first or last, so reversing is also meaningless.HashSetis excluded.
This is why when you look at the updated Java collections hierarchy, you will notice that SequencedCollection does not sit above Queue or HashSet. It only covers the collections that can guarantee a meaningful sequence.
Where Do the New Interfaces Fit?
Here is the updated hierarchy in simplified form:
Iterable
└── Collection
└── SequencedCollection <-- NEW
├── List
│ ├── ArrayList
│ └── LinkedList
├── Deque
│ ├── ArrayDeque
│ └── LinkedList
└── SequencedSet <-- NEW
├── LinkedHashSet
└── SortedSet
└── TreeSet
Map
└── SequencedMap <-- NEW
├── LinkedHashMap
└── SortedMap
└── TreeMapSequencedCollection sits directly under Collection and above List and Deque. Then SequencedSet extends SequencedCollection and sits above LinkedHashSet and SortedSet. And SequencedMap is a separate interface that sits above LinkedHashMap and SortedMap.
You might wonder why SequencedSet is a separate interface rather than just letting LinkedHashSet extend SequencedCollection directly. The reason is duplicates. A List and a Deque allow duplicate elements. A Set by definition does not. So SequencedSet needs to express this no duplicates constraint separately, inheriting from both SequencedCollection and Set. It is a clean separation of concerns.
The SequencedCollection Interface
The SequencedCollection interface is defined like this:
java
public interface SequencedCollection<E> extends Collection<E> {
SequencedCollection<E> reversed();
default void addFirst(E e) {
throw new UnsupportedOperationException();
}
default void addLast(E e) {
throw new UnsupportedOperationException();
}
default E getFirst() {
return this.iterator().next();
}
default E getLast() {
// ...
}
default E removeFirst() {
// ...
}
default E removeLast() {
// ...
}
}Notice that most methods are default methods, meaning they have a fallback implementation. The only abstract method is reversed(), which every implementing class must provide its own version of.
Let us walk through each method in detail.
getFirst() and getLast()
These give you the first and last elements of the collection without any awkward index arithmetic or iteration.
java
import java.util.ArrayList;
import java.util.List;
public class SequencedDemo {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("apple");
fruits.add("banana");
fruits.add("cherry");
// Before Java 21 you had to write:
String oldFirst = fruits.get(0);
String oldLast = fruits.get(fruits.size() - 1);
// After Java 21:
String first = fruits.getFirst(); // "apple"
String last = fruits.getLast(); // "cherry"
System.out.println("First: " + first);
System.out.println("Last: " + last);
}
}The same methods work uniformly on a Deque:
java
import java.util.ArrayDeque;
import java.util.Deque;
Deque<Integer> deque = new ArrayDeque<>();
deque.add(10);
deque.add(20);
deque.add(30);
System.out.println(deque.getFirst()); // 10
System.out.println(deque.getLast()); // 30And on a LinkedHashSet:
java
import java.util.LinkedHashSet;
LinkedHashSet<String> names = new LinkedHashSet<>();
names.add("Zara");
names.add("Mia");
names.add("Leo");
// No more iterator tricks!
System.out.println(names.getFirst()); // "Zara"
System.out.println(names.getLast()); // "Leo"One consistent API across all these different collection types. That is the power of SequencedCollection.
addFirst() and addLast()
These let you insert elements at the beginning or end of the sequence.
java
List<String> cities = new ArrayList<>();
cities.add("London");
cities.add("Tokyo");
cities.addFirst("Paris"); // Insert at the beginning
cities.addLast("Sydney"); // Insert at the end
System.out.println(cities);
// Output: [Paris, London, Tokyo, Sydney]For a List, addFirst("Paris") is equivalent to add(0, "Paris"). For a Deque, it calls push() or addFirst() from the old Deque API. The new interface just standardizes the name.
Note an important nuance for SortedSet: calling addFirst() or addLast() on a TreeSet will throw UnsupportedOperationException unless the element you add would naturally land at the first or last position according to the sorted order. This is because a TreeSet cannot let you force placement. If you try to add an element to the "first" position but it belongs in the middle alphabetically, the operation throws an exception. This is by design because the sorted invariant must be maintained.
removeFirst() and removeLast()
These remove and return the first or last element.
java
List<String> queue = new ArrayList<>();
queue.add("task1");
queue.add("task2");
queue.add("task3");
String removed = queue.removeFirst(); // "task1"
System.out.println(removed);
System.out.println(queue); // [task2, task3]
String lastRemoved = queue.removeLast(); // "task3"
System.out.println(lastRemoved);
System.out.println(queue); // [task2]This is especially useful in processing pipelines where you consume elements from either end.
reversed()
This is the only abstract method in the interface, meaning every implementing class provides its own implementation. The key insight is that reversed() returns a view, not a copy. This is important for performance.
java
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);
numbers.add(4);
numbers.add(5);
// Get a reversed view
List<Integer> reversedView = numbers.reversed();
System.out.println(reversedView);
// Output: [5, 4, 3, 2, 1]
// If you modify the original, the view reflects the change
numbers.add(6);
System.out.println(reversedView);
// Output: [6, 5, 4, 3, 2, 1]Because it is a view backed by the original collection, modifications to either the original or the reversed view are reflected in both. This is efficient because no data is actually copied. Think of it like a mirror: the mirror does not duplicate the room, it just shows it from the other direction.
If you want an independent reversed copy, you need to explicitly create one:
java
List<Integer> reversedCopy = new ArrayList<>(numbers.reversed());The SequencedSet Interface
SequencedSet extends both SequencedCollection and Set. Its definition is concise:
java
public interface SequencedSet<E> extends SequencedCollection<E>, Set<E> {
@Override
SequencedSet<E> reversed();
}The only thing it overrides is the return type of reversed(). When you call reversed() on a SequencedSet, you get back a SequencedSet, not just a SequencedCollection. This is important because you want to preserve the type information.
java
import java.util.LinkedHashSet;
LinkedHashSet<String> colors = new LinkedHashSet<>();
colors.add("red");
colors.add("green");
colors.add("blue");
// getFirst and getLast work perfectly
System.out.println(colors.getFirst()); // "red"
System.out.println(colors.getLast()); // "blue"
// Reverse is a view of the set in reverse insertion order
var reversed = colors.reversed();
System.out.println(reversed);
// Output: [blue, green, red]For TreeSet, which follows sorted order rather than insertion order, the reversed() method gives you a descending view:
java
import java.util.TreeSet;
TreeSet<Integer> numbers = new TreeSet<>();
numbers.add(14);
numbers.add(5);
numbers.add(7);
// TreeSet stores in sorted ascending order: [5, 7, 14]
System.out.println(numbers.getFirst()); // 5
System.out.println(numbers.getLast()); // 14
// Reversed gives descending view
System.out.println(numbers.reversed()); // [14, 7, 5]Remember the earlier point about addFirst() and addLast() on a TreeSet: because the sorted order must be maintained, you can only successfully call addFirst(e) if e is actually smaller than every existing element, and addLast(e) only if e is larger than every existing element. Otherwise you get an UnsupportedOperationException.
The SequencedMap Interface
SequencedMap is a separate hierarchy because Map does not extend Collection. Its definition looks like this:
java
public interface SequencedMap<K, V> extends Map<K, V> {
SequencedMap<K, V> reversed();
default Map.Entry<K, V> firstEntry() { ... }
default Map.Entry<K, V> lastEntry() { ... }
default Map.Entry<K, V> pollFirstEntry() { ... }
default Map.Entry<K, V> pollLastEntry() { ... }
default K firstKey() { ... }
default K lastKey() { ... }
default V putFirst(K k, V v) { ... }
default V putLast(K k, V v) { ... }
default SequencedSet<K> sequencedKeySet() { ... }
default SequencedCollection<V> sequencedValues() { ... }
default SequencedSet<Map.Entry<K, V>> sequencedEntrySet() { ... }
}The map equivalent of getFirst is firstEntry() and lastEntry(), which return a Map.Entry giving you both the key and value. pollFirstEntry() and pollLastEntry() remove and return those entries.
The sequencedKeySet(), sequencedValues(), and sequencedEntrySet() methods return views of the map's keys, values, and entries as sequenced collections, so you can use all the SequencedCollection methods on them.
java
import java.util.LinkedHashMap;
LinkedHashMap<String, Integer> scores = new LinkedHashMap<>();
scores.put("Alice", 95);
scores.put("Bob", 87);
scores.put("Carol", 92);
// Get first and last entries
System.out.println(scores.firstEntry()); // Alice=95
System.out.println(scores.lastEntry()); // Carol=92
// Get keys as a sequenced set
scores.sequencedKeySet().forEach(System.out::println);
// Alice, Bob, Carol
// Reversed view
System.out.println(scores.reversed().firstEntry()); // Carol=92For TreeMap, which stores keys in sorted order:
java
import java.util.TreeMap;
TreeMap<String, Integer> sorted = new TreeMap<>();
sorted.put("banana", 2);
sorted.put("apple", 1);
sorted.put("cherry", 3);
// TreeMap sorts alphabetically
System.out.println(sorted.firstEntry()); // apple=1
System.out.println(sorted.lastEntry()); // cherry=3
System.out.println(sorted.firstKey()); // "apple"
System.out.println(sorted.lastKey()); // "cherry"A Complete Before and After Comparison
Here is a side by side comparison showing exactly what changed for common operations:
java
import java.util.*;
public class BeforeAndAfter {
public static void main(String[] args) {
// === LIST ===
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
// Before Java 21
String oldListFirst = list.get(0);
String oldListLast = list.get(list.size() - 1);
// After Java 21
String newListFirst = list.getFirst(); // "a"
String newListLast = list.getLast(); // "c"
// === DEQUE ===
Deque<String> deque = new ArrayDeque<>(List.of("x", "y", "z"));
// Before Java 21 - already had getFirst/getLast but only on Deque
String dequeFirst = deque.getFirst(); // "x"
// After Java 21 - same methods, now unified under SequencedCollection
String dequeFirstNew = deque.getFirst(); // "x" - same
// === LINKED HASH SET ===
LinkedHashSet<String> lhs = new LinkedHashSet<>(List.of("p", "q", "r"));
// Before Java 21 - painful
String oldSetFirst = lhs.iterator().next();
String oldSetLast = null;
for (String s : lhs) oldSetLast = s;
// After Java 21 - clean
String newSetFirst = lhs.getFirst(); // "p"
String newSetLast = lhs.getLast(); // "r"
System.out.println("List first: " + newListFirst);
System.out.println("Set first: " + newSetFirst);
System.out.println("Set last: " + newSetLast);
}
}Interview Questions and Key Pitfalls
You will encounter these questions in Java interviews when the conversation turns to Java 21 features. Knowing the answers cold will set you apart.
What problem do the Java 21 sequenced collection interfaces solve?
Before Java 21, different collection types used inconsistent methods to access, add, or remove elements at the first or last position. List used index based access, Deque had its own named methods, and LinkedHashSet required iterator tricks. There was no common interface for ordered collections. Java 21 introduced SequencedCollection, SequencedSet, and SequencedMap to provide a unified API for all ordered collections.
What are the three conditions a collection must satisfy to be considered sequenced?
First, it must have a predictable iteration order, either insertion order or sorted order. Second, it must have a well defined first and last element. Third, it must support a reversed view. Collections like HashSet and PriorityQueue fail these conditions and are therefore excluded.
Why is reversed() a view and not a copy?
For performance. Creating a full copy of a large collection just to traverse it in reverse would be wasteful in memory and time. A view is backed by the original collection, so no data is duplicated. Any changes to the original are reflected in the reversed view and vice versa.
Why is SequencedSet a separate interface from SequencedCollection?
Because Set has an additional contract that Collection does not: no duplicate elements are allowed. SequencedSet combines SequencedCollection with Set to express both ordered access and uniqueness. If LinkedHashSet simply extended SequencedCollection, the no duplicates guarantee would be lost at the interface level.
What happens if you call addFirst() on a TreeSet?
It will throw UnsupportedOperationException unless the element you are adding would naturally become the new smallest element in the sorted order. A TreeSet cannot allow you to force an element into the first position if the sorted order would place it elsewhere. The sorted invariant takes priority.
What is the difference between SequencedCollection and the old Deque interface?
Deque already had getFirst(), getLast(), addFirst(), addLast(), removeFirst(), and removeLast(). The problem was these methods only existed on Deque. List, LinkedHashSet, and maps had no common interface for these operations. SequencedCollection lifts this contract to a higher level, making it available to any ordered collection, not just Deque.
Does SequencedMap extend SequencedCollection?
No. Map itself does not extend Collection in Java's hierarchy. So SequencedMap extends Map directly and defines its own parallel set of operations: firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry(), putFirst(), putLast(), and reversed().
Which collections are NOT covered by the sequenced interfaces?
HashSet, HashMap, Queue, and PriorityQueue are not covered. HashSet and HashMap have no predictable ordering. Queue is designed for FIFO processing where you only care about the head. PriorityQueue orders by priority weight, not insertion or sorted order in the traditional sense.
Can you write a method that works on any sequenced collection uniformly?
Yes, and that is one of the main benefits. Before Java 21 you could not write a single generic method that retrieved the first element of a List, a LinkedHashSet, and a Deque without overloading or type checking. Now you can accept SequencedCollection<T> as a parameter and call getFirst() on anything.
java
public static <T> T getFirstElement(SequencedCollection<T> collection) {
return collection.getFirst();
}
// Works with any sequenced collection
getFirstElement(new ArrayList<>(List.of(1, 2, 3))); // 1
getFirstElement(new ArrayDeque<>(List.of("a", "b", "c"))); // "a"
getFirstElement(new LinkedHashSet<>(List.of("x", "y"))); // "x"This kind of polymorphism was impossible before Java 21. Now it is trivial.
Why This Matters in Real Code
You might think this is a small quality of life improvement, but its impact compounds over time. Consider a team working on a large codebase where several developers independently write utilities to get the first or last element of different collection types. Without a common interface, each developer writes slightly different code. Code reviews become cluttered with discussions about the right approach. Bugs creep in when someone forgets to handle the size() - 1 edge case or creates unnecessary intermediate lists for reversal.
With SequencedCollection, there is one right answer for every situation. New developers can learn the API once and apply it everywhere. Generic algorithms become possible. Library authors can write utilities that work across all ordered collections without requiring callers to convert to a specific type.
The reversed view is particularly elegant. Previously, if you wanted to process a LinkedHashSet in reverse order, you had to copy it into a List, reverse the list, then iterate. Now you call reversed() and iterate directly. Zero allocation, zero copying, one method call.
Summary
Java 21 added SequencedCollection, SequencedSet, and SequencedMap to solve a longstanding inconsistency in the collections framework. Before these interfaces, every ordered collection type used different methods to access the first and last elements, add elements at either end, and reverse the sequence.
A collection qualifies as sequenced if it maintains predictable iteration order (insertion or sorted), has well defined first and last elements, and supports reversal. This excludes HashSet, HashMap, Queue, and PriorityQueue.
SequencedCollection provides six core methods: getFirst(), getLast(), addFirst(), addLast(), removeFirst(), removeLast(), and the abstract reversed(). The reversed() method returns a view backed by the original collection, not a copy.
SequencedSet extends SequencedCollection and Set to cover ordered collections without duplicates, including LinkedHashSet and TreeSet. SequencedMap extends Map directly and provides equivalent methods for key value pairs.
These interfaces make your code cleaner, your APIs more expressive, and enable powerful generic programming patterns that were not possible before Java 21.