For decades, Java’s Collections Framework had a glaring inconsistency. Want the first element of a List? Call list.get(0). First element of a TreeSet? Use set.first(). First entry of a LinkedHashMap? You’d better iterate or call entrySet().iterator().next(). This chaos ended with JEP 431, which introduced Sequenced Collections in Java 21—a simple idea that should have existed from day one.
The Problem Nobody Talked About
Java’s collection interfaces evolved organically over 25+ years, and it shows. Each collection type developed its own vocabulary for accessing elements at specific positions. Lists had numerical indexing. Deques had getFirst() and getLast(). Sorted sets had first() and last(). But there was no common abstraction for “collections that have a defined encounter order.”
This wasn’t just an aesthetic problem. It made writing generic algorithms painful. Want to write a method that works with any ordered collection? Good luck. You’d end up with instanceof checks and special cases for each collection type.
// Before Java 21 - the instanceof mess
public static <T> T getFirstElement(Collection<T> collection) {
if (collection instanceof List) {
return ((List<T>) collection).get(0);
} else if (collection instanceof Deque) {
return ((Deque<T>) collection).getFirst();
} else if (collection instanceof SortedSet) {
return ((SortedSet<T>) collection).first();
} else if (collection instanceof LinkedHashSet) {
return collection.iterator().next();
}
throw new IllegalArgumentException("Unsupported collection type");
}
Every Java developer has written code like this. It’s verbose, error-prone, and fundamentally wrong—we’re checking runtime types instead of relying on interfaces.
Enter Sequenced Collections
The Sequenced Collections API introduces three new interfaces that sit between Collection and the concrete implementations:
- SequencedCollection – A collection with a defined encounter order
- SequencedSet – A set with a defined encounter order
- SequencedMap – A map with a defined encounter order for entries
These interfaces provide uniform methods for accessing and manipulating elements at both ends:
interface SequencedCollection<E> extends Collection<E> {
SequencedCollection<E> reversed();
void addFirst(E e);
void addLast(E e);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
Now your generic algorithm becomes trivial:
// Java 21+ - works with any sequenced collection
public static <T> T getFirstElement(SequencedCollection<T> collection) {
return collection.getFirst();
}
No instanceof checks. No special cases. Just polymorphism working as intended.
Reversed Views: Efficiency Meets Elegance
The reversed() method is deceptively powerful. It doesn’t create a new collection or copy elements—it returns a live view of the original collection in reverse order. Modifications to the view affect the original, and vice versa.
List<String> list = new ArrayList<>(List.of("A", "B", "C", "D"));
SequencedCollection<String> reversed = list.reversed();
System.out.println(reversed); // [D, C, B, A]
reversed.addFirst("E"); // Adds to the end of original list
System.out.println(list); // [A, B, C, D, E]
System.out.println(reversed); // [E, D, C, B, A]
This is crucial for performance. Reversing a large list now costs O(1) instead of O(n). You’re not copying millions of elements—you’re just changing the perspective on the same data.
The reversed view maintains all the properties of the original collection. If the original is a List, the reversed view supports random access with the same performance characteristics. If it’s a Set, the reversed view maintains set semantics.
Map Symmetry: Finally
Maps were particularly painful before sequenced collections. LinkedHashMap maintains insertion order, but accessing the first or last entry required awkward iteration:
// Old way - verbose and inefficient
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("first", 1);
map.put("second", 2);
Map.Entry<String, Integer> firstEntry =
map.entrySet().iterator().next();
// Getting the last entry? Even worse
Map.Entry<String, Integer> lastEntry = null;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
lastEntry = entry;
}
SequencedMap makes this elegant:
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("first", 1);
map.put("second", 2);
Map.Entry<String, Integer> firstEntry = map.firstEntry();
Map.Entry<String, Integer> lastEntry = map.lastEntry();
// Or just the keys/values
String firstKey = map.sequencedKeySet().getFirst();
Integer lastValue = map.sequencedValues().getLast();
The sequencedKeySet() and sequencedValues() methods return SequencedSet and SequencedCollection views respectively, giving you ordered access to map components.
Deque Integration: Smoothing the Rough Edges
Deque already had methods like getFirst() and addLast(), so why change anything? Consistency. The Deque interface now extends SequencedCollection, and its existing methods serve as the implementation of the sequenced contract.
But there’s a subtle improvement. Deque’s original methods had conflicting documentation about what happens with empty deques—some threw exceptions, others returned null. SequencedCollection standardizes this: getFirst() and getLast() throw NoSuchElementException on empty collections, matching the behavior of first() and last() in SortedSet.
Deque<String> deque = new ArrayDeque<>();
// Both interfaces, same methods, consistent behavior
deque.addFirst("item"); // From Deque
deque.getFirst(); // From SequencedCollection
deque.reversed().addFirst("item2"); // Adds to end via reversed view
Which Collections Are Sequenced?
Not every collection has a defined order. HashSet and HashMap explicitly don’t guarantee encounter order. The new interfaces only apply to collections where order matters:
SequencedCollection implementations:
- List (ArrayList, LinkedList, Vector, Stack)
- Deque (ArrayDeque, LinkedList)
- LinkedHashSet
- SortedSet (TreeSet and subclasses)
SequencedMap implementations:
- LinkedHashMap
- SortedMap (TreeMap and subclasses)
Notably absent: HashSet, HashMap, and EnumMap. These don’t implement the sequenced interfaces because they don’t guarantee consistent ordering.
Sorted Collections: Natural Sequencing
SortedSet and SortedMap already had ordering—they sort elements by natural order or a custom comparator. These now extend the sequenced interfaces, providing uniform access methods alongside their existing first(), last(), and subset operations.
SortedSet<Integer> sortedSet = new TreeSet<>(List.of(3, 1, 4, 1, 5, 9, 2, 6)); // Both work - sorted interface methods int smallest = sortedSet.first(); // 1 // And sequenced interface methods int alsoSmallest = sortedSet.getFirst(); // 1 int largest = sortedSet.getLast(); // 9 // Reversed view maintains sorted properties SequencedSet<Integer> descending = sortedSet.reversed(); System.out.println(descending); // [9, 6, 5, 4, 3, 2, 1]
The reversed view of a SortedSet is still sorted—just in reverse order. This maintains the collection’s invariants while providing bidirectional access.
Edge Cases and Exceptions
The API carefully defines failure behavior. Attempting to access elements from an empty collection throws NoSuchElementException:
List<String> emptyList = new ArrayList<>();
try {
emptyList.getFirst(); // Throws NoSuchElementException
} catch (NoSuchElementException e) {
System.out.println("List is empty");
}
This differs from get(0) which throws IndexOutOfBoundsException. The distinction is semantic—NoSuchElementException indicates the absence of an element, while IndexOutOfBoundsException indicates an invalid index. Both signal errors, but with different meanings.
For unmodifiable collections, mutation operations throw UnsupportedOperationException:
List<String> immutable = List.of("A", "B", "C");
immutable.addFirst("Z"); // UnsupportedOperationException
immutable.reversed().removeFirst(); // Also throws
The reversed view inherits mutability characteristics from the original collection. If the original is unmodifiable, so is the view.
Real-World Impact: Cleaner APIs
The biggest benefit isn’t in application code—it’s in library and framework design. APIs can now accept SequencedCollection instead of List when they need ordered access but don’t require random access or indexing.
// Before - overspecified requirement
public void processInOrder(List<Task> tasks) {
Task first = tasks.get(0);
Task last = tasks.get(tasks.size() - 1);
// Process tasks
}
// After - more flexible contract
public void processInOrder(SequencedCollection<Task> tasks) {
Task first = tasks.getFirst();
Task last = tasks.getLast();
// Process tasks - now works with Deque, LinkedHashSet, etc.
}
This makes APIs more flexible without sacrificing type safety. Callers can pass any ordered collection, not just lists.
Performance Characteristics
Most sequenced operations are constant-time O(1) for well-designed collections:
- ArrayList:
getFirst()is O(1),getLast()is O(1), butaddFirst()is O(n) due to shifting - LinkedList: All sequenced operations are O(1)
- ArrayDeque: All sequenced operations are O(1)
- TreeSet:
getFirst()andgetLast()are O(log n), removals are O(log n)
The reversed view itself is always O(1) to create, but operations on the view have the same complexity as operations on the original collection.
Understanding these characteristics helps you choose the right collection. Need frequent additions at both ends? Use ArrayDeque or LinkedList, not ArrayList.
Migration Path: Backward Compatibility
The JDK team designed these interfaces with full backward compatibility. All existing collection implementations gained the new methods through default interface implementations where possible, or through explicit additions.
Your existing code continues to work unchanged. If you have a method accepting List, it still accepts List—which now happens to also implement SequencedCollection.
// Old code still works
public void oldMethod(List<String> list) {
String first = list.get(0); // Still valid
}
// New code can be more flexible
public void newMethod(SequencedCollection<String> collection) {
String first = collection.getFirst(); // More general
}
The migration path is opt-in. You can adopt sequenced collections gradually, starting with new APIs and migrating existing ones as needed.
Beyond Java: Language Evolution
Sequenced Collections represent a pattern seen across language evolution: filling gaps that seemed obvious in retrospect. Python’s collections had consistent indexing from day one. C#’s LINQ provides uniform access across collection types. Rust’s Iterator trait gives every collection a common vocabulary.
Java took longer to get here, but the result is thoughtfully designed and fully compatible with 25 years of existing collections code. That’s no small feat for a language with Java’s backward-compatibility requirements.
The lesson for language designers? Start with strong abstractions. The collection hierarchy should capture semantic properties—ordered vs unordered, indexed vs sequential, mutable vs immutable—from the beginning. Retrofitting is possible but messy.
What’s Still Missing
Sequenced Collections solve the “first and last element” problem elegantly, but some operations remain awkward. Want the second element? You still need list.get(1) for lists or collection.stream().skip(1).findFirst() for other sequenced collections.
There’s no standard way to slice a sequenced collection (get elements 5 through 10). List has subList(), but SequencedCollection doesn’t mandate this. Each implementation might offer different capabilities.
The API also doesn’t address concurrent collections. ConcurrentLinkedQueue and ConcurrentLinkedDeque don’t implement SequencedCollection because their ordering guarantees are weaker in concurrent scenarios. This leaves a gap for concurrent ordered collections.
These are minor quibbles. The API delivers exactly what it promises: uniform access to the endpoints of ordered collections. Future JEPs might address additional scenarios, but the foundation is solid.
Useful Links
Official Documentation:
- JEP 431: Sequenced Collections – Official Java Enhancement Proposal with full specification
- Java 21 API Documentation – Complete API reference for sequenced interfaces
- Java Collections Framework – Oracle’s official collections tutorial
Deep Dives and Analysis:
- Inside Java Podcast on Sequenced Collections – Discussion with JEP author Stuart Marks
- Baeldung Sequenced Collections Guide – Practical examples and use cases
- JEP Café: Sequenced Collections – Video walkthrough of the feature
Migration and Best Practices:
- Java 21 Migration Guide – Official migration documentation
- Sequenced Collections in Practice – Java Developer Relations guide
Community Discussions:
- Reddit r/java Discussion – Community reactions and use cases
- Stack Overflow: Sequenced Collections – Real-world questions and answers
Related Java 21 Features:
- Java 21 Release Notes – Complete list of new features
- Pattern Matching for Switch – Another major Java 21 enhancement
- Virtual Threads – Concurrent programming improvements in Java 21
Thank you!
We will contact you soon.
Eleftheria DrosopoulouNovember 7th, 2025Last Updated: October 31st, 2025

This site uses Akismet to reduce spam. Learn how your comment data is processed.