Java 21 introduced Sequenced Collections as a preview feature (JEP 431) that provides a consistent API for collections with defined encounter orders. These new interfaces—SequencedSet, SequencedMap, and SequencedCollection—offer standardized methods for accessing first and last elements and reversing views.
Overview of Sequenced Collections
The Problem Before Java 21
Before Java 21, different collection types had inconsistent APIs for accessing endpoints:
ListhadgetFirst(),getLast()DequehadgetFirst(),getLast(),getFirst(),getLast()SortedSethadfirst(),last()LinkedHashSethad no direct endpoint access
The Solution: Sequenced Collections
Java 21 introduces a unified hierarchy:
Collection └── SequencedCollection ├── List ├── Deque └── SequencedSet ├── LinkedHashSet └── SortedSet/NavigableSet Map └── SequencedMap ├── LinkedHashMap └── SortedMap/NavigableMap
SequencedSet Interface
Definition and Methods
interface SequencedSet<E> extends SequencedCollection<E>, Set<E> { // Inherits from SequencedCollection: // E getFirst(); // E getLast(); // void addFirst(E); // void addLast(E); // E removeFirst(); // E removeLast(); // SequencedSet-specific: SequencedSet<E> reversed(); } Example 1: Basic SequencedSet Operations
import java.util.*; public class SequencedSetDemo { public static void main(String[] args) { // Create a SequencedSet (LinkedHashSet implements SequencedSet) SequencedSet<String> fruitSet = new LinkedHashSet<>(); // Add elements - maintains insertion order fruitSet.add("Apple"); fruitSet.add("Banana"); fruitSet.add("Cherry"); fruitSet.add("Date"); System.out.println("Original set: " + fruitSet); // Access first and last elements System.out.println("First element: " + fruitSet.getFirst()); System.out.println("Last element: " + fruitSet.getLast()); // Add elements at ends fruitSet.addFirst("Apricot"); // Adds to beginning fruitSet.addLast("Elderberry"); // Adds to end System.out.println("After adding first/last: " + fruitSet); // Remove elements from ends String removedFirst = fruitSet.removeFirst(); String removedLast = fruitSet.removeLast(); System.out.println("Removed first: " + removedFirst); System.out.println("Removed last: " + removedLast); System.out.println("After removals: " + fruitSet); // Get reversed view SequencedSet<String> reversedSet = fruitSet.reversed(); System.out.println("Reversed view: " + reversedSet); // Modifications through reversed view affect original reversedSet.addFirst("Zucchini"); System.out.println("After adding to reversed: " + fruitSet); } } Output:
Original set: [Apple, Banana, Cherry, Date] First element: Apple Last element: Date After adding first/last: [Apricot, Apple, Banana, Cherry, Date, Elderberry] Removed first: Apricot Removed last: Elderberry After removals: [Apple, Banana, Cherry, Date] Reversed view: [Date, Cherry, Banana, Apple] After adding to reversed: [Zucchini, Apple, Banana, Cherry, Date]
Example 2: SequencedSet with SortedSet
import java.util.*; public class SortedSetSequencedDemo { public static void main(String[] args) { // TreeSet also implements SequencedSet SequencedSet<Integer> numbers = new TreeSet<>(); numbers.add(30); numbers.add(10); numbers.add(20); numbers.add(40); System.out.println("Sorted set: " + numbers); System.out.println("First: " + numbers.getFirst()); // 10 System.out.println("Last: " + numbers.getLast()); // 40 // reversed() returns a view in descending order SequencedSet<Integer> descending = numbers.reversed(); System.out.println("Descending view: " + descending); // Attempting mutating operations on sorted sets try { numbers.addFirst(5); // Would break sorting - throws UnsupportedOperationException } catch (UnsupportedOperationException e) { System.out.println("addFirst not supported by TreeSet"); } try { numbers.addLast(50); // Would break sorting - throws UnsupportedOperationException } catch (UnsupportedOperationException e) { System.out.println("addLast not supported by TreeSet"); } } } Output:
Sorted set: [10, 20, 30, 40] First: 10 Last: 40 Descending view: [40, 30, 20, 10] addFirst not supported by TreeSet addLast not supported by TreeSet
SequencedMap Interface
Definition and Methods
interface SequencedMap<K, V> extends Map<K, V> { // Entry accessors Entry<K, V> firstEntry(); Entry<K, V> lastEntry(); // Key accessors K firstKey(); K lastKey(); // Element addition Entry<K, V> pollFirstEntry(); Entry<K, V> pollLastEntry(); // Element removal Entry<K, V> putFirst(K key, V value); Entry<K, V> putLast(K key, V value); // Reversed view SequencedMap<K, V> reversed(); // Sequenced views SequencedSet<K> sequencedKeySet(); SequencedCollection<V> sequencedValues(); SequencedSet<Entry<K, V>> sequencedEntrySet(); } Example 3: Basic SequencedMap Operations
import java.util.*; public class SequencedMapDemo { public static void main(String[] args) { // LinkedHashMap implements SequencedMap SequencedMap<String, Integer> studentScores = new LinkedHashMap<>(); // Add elements in order studentScores.put("Alice", 85); studentScores.put("Bob", 92); studentScores.put("Charlie", 78); studentScores.put("Diana", 95); System.out.println("Original map: " + studentScores); // Access first and last entries System.out.println("First entry: " + studentScores.firstEntry()); System.out.println("Last entry: " + studentScores.lastEntry()); System.out.println("First key: " + studentScores.firstKey()); System.out.println("Last key: " + studentScores.lastKey()); // Add elements at ends studentScores.putFirst("Aaron", 88); // Adds to beginning studentScores.putLast("Zoe", 91); // Adds to end System.out.println("After putFirst/putLast: " + studentScores); // Poll (remove and return) entries from ends Map.Entry<String, Integer> polledFirst = studentScores.pollFirstEntry(); Map.Entry<String, Integer> polledLast = studentScores.pollLastEntry(); System.out.println("Polled first: " + polledFirst); System.out.println("Polled last: " + polledLast); System.out.println("After polling: " + studentScores); // Get reversed view SequencedMap<String, Integer> reversedMap = studentScores.reversed(); System.out.println("Reversed map: " + reversedMap); // Sequenced views System.out.println("Sequenced keys: " + studentScores.sequencedKeySet()); System.out.println("Sequenced values: " + studentScores.sequencedValues()); System.out.println("Sequenced entries: " + studentScores.sequencedEntrySet()); } } Output:
Original map: {Alice=85, Bob=92, Charlie=78, Diana=95} First entry: Alice=85 Last entry: Diana=95 First key: Alice Last key: Diana After putFirst/putLast: {Aaron=88, Alice=85, Bob=92, Charlie=78, Diana=95, Zoe=91} Polled first: Aaron=88 Polled last: Zoe=91 After polling: {Alice=85, Bob=92, Charlie=78, Diana=95} Reversed map: {Diana=95, Charlie=78, Bob=92, Alice=85} Sequenced keys: [Alice, Bob, Charlie, Diana] Sequenced values: [85, 92, 78, 95] Sequenced entries: [Alice=85, Bob=92, Charlie=78, Diana=95] Example 4: SequencedMap with SortedMap
import java.util.*; public class SortedMapSequencedDemo { public static void main(String[] args) { // TreeMap implements SequencedMap SequencedMap<Integer, String> products = new TreeMap<>(); products.put(103, "Keyboard"); products.put(101, "Mouse"); products.put(104, "Monitor"); products.put(102, "Headphones"); System.out.println("Sorted map: " + products); System.out.println("First entry: " + products.firstEntry()); System.out.println("Last entry: " + products.lastEntry()); // Reversed view (descending order) SequencedMap<Integer, String> descendingProducts = products.reversed(); System.out.println("Descending order: " + descendingProducts); // Poll operations work with sorted maps Map.Entry<Integer, String> firstProduct = products.pollFirstEntry(); Map.Entry<Integer, String> lastProduct = products.pollLastEntry(); System.out.println("Removed first: " + firstProduct); System.out.println("Removed last: " + lastProduct); System.out.println("After polling: " + products); // putFirst/putLast are not supported by TreeMap try { products.putFirst(100, "Webcam"); } catch (UnsupportedOperationException e) { System.out.println("putFirst not supported by TreeMap"); } } } Output:
Sorted map: {101=Mouse, 102=Headphones, 103=Keyboard, 104=Monitor} First entry: 101=Mouse Last entry: 104=Monitor Descending order: {104=Monitor, 103=Keyboard, 102=Headphones, 101=Mouse} Removed first: 101=Mouse Removed last: 104=Monitor After polling: {102=Headphones, 103=Keyboard} putFirst not supported by TreeMap Practical Use Cases
Example 5: LRU (Least Recently Used) Cache
import java.util.*; public class LRUCache<K, V> { private final SequencedMap<K, V> cache; private final int maxSize; public LRUCache(int maxSize) { this.maxSize = maxSize; this.cache = new LinkedHashMap<>(maxSize, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { return size() > maxSize; } }; } public V get(K key) { return cache.get(key); } public void put(K key, V value) { cache.put(key, value); } public V getRecent() { return cache.isEmpty() ? null : cache.getLast(); } public V getOldest() { return cache.isEmpty() ? null : cache.getFirst(); } public void displayCache() { System.out.println("Cache (oldest to newest): " + cache); System.out.println("Oldest: " + getOldest()); System.out.println("Newest: " + getRecent()); } public static void main(String[] args) { LRUCache<String, String> cache = new LRUCache<>(3); cache.put("A", "Data A"); cache.put("B", "Data B"); cache.put("C", "Data C"); cache.displayCache(); // Access pattern affects order cache.get("A"); // Moves A to most recent cache.displayCache(); // Adding new element evicts oldest cache.put("D", "Data D"); cache.displayCache(); } } Output:
Cache (oldest to newest): {A=Data A, B=Data B, C=Data C} Oldest: Data A Newest: Data C Cache (oldest to newest): {B=Data B, C=Data C, A=Data A} Oldest: Data B Newest: Data A Cache (oldest to newest): {C=Data C, A=Data A, D=Data D} Oldest: Data C Newest: Data D Example 6: Navigation History
import java.util.*; public class NavigationHistory { private final SequencedDeque<String> history = new LinkedHashSet<>(); private final int maxHistorySize; public NavigationHistory(int maxHistorySize) { this.maxHistorySize = maxHistorySize; } public void visitPage(String url) { history.remove(url); // Remove if already exists (avoid duplicates) history.addLast(url); // Add as most recent // Trim history if exceeds max size while (history.size() > maxHistorySize) { history.removeFirst(); } } public String goBack() { if (history.size() < 2) { return null; } // Remove current history.removeLast(); // Return previous return history.getLast(); } public List<String> getHistory() { return new ArrayList<>(history); } public List<String> getRecentFirst() { List<String> recentFirst = new ArrayList<>(history.reversed()); return recentFirst; } public static void main(String[] args) { NavigationHistory nav = new NavigationHistory(5); nav.visitPage("home"); nav.visitPage("products"); nav.visitPage("product-details"); nav.visitPage("cart"); nav.visitPage("checkout"); System.out.println("History (oldest first): " + nav.getHistory()); System.out.println("History (recent first): " + nav.getRecentFirst()); // Go back String previous = nav.goBack(); System.out.println("Went back to: " + previous); System.out.println("Current history: " + nav.getHistory()); // Visit new page nav.visitPage("confirmation"); System.out.println("After new visit: " + nav.getHistory()); } } Migration Guide
Before Java 21 vs After Java 21
Before Java 21:
// Inconsistent APIs List<String> list = new ArrayList<>(); String first = list.get(0); // or list.isEmpty() ? null : list.get(0) Deque<String> deque = new ArrayDeque<>(); String first = deque.getFirst(); SortedSet<String> sortedSet = new TreeSet<>(); String first = sortedSet.first(); LinkedHashSet<String> linkedSet = new LinkedHashSet<>(); // No direct way to get first/last!
After Java 21:
// Unified API SequencedCollection<String> collection = ...; String first = collection.getFirst(); String last = collection.getLast(); SequencedSet<String> set = ...; String first = set.getFirst(); SequencedMap<String, Integer> map = ...; Map.Entry<String, Integer> firstEntry = map.firstEntry();
Key Benefits
- Consistent API: Unified methods across different collection types
- Improved Readability: Clear intent when accessing collection endpoints
- Reversible Views: Built-in support for reversed iteration
- Reduced Boilerplate: No need for workarounds to access first/last elements
- Enhanced Functionality: Additional operations like
addFirst,addLast
Implementation Support
SequencedSet implementations:
LinkedHashSetTreeSet(read-only for mutation operations)ConcurrentSkipListSet(read-only for mutation operations)
SequencedMap implementations:
LinkedHashMapTreeMap(read-only forputFirst/putLast)ConcurrentSkipListMap(read-only forputFirst/putLast)
Conclusion
Sequenced Collections in Java 21 provide:
- Standardized endpoint access across collection types
- Reversible views for bidirectional traversal
- Consistent mutation operations where supported
- Backward compatibility with existing collections
When to use SequencedSet/SequencedMap:
- When you need consistent access to collection endpoints
- For ordered collections where position matters
- When building navigation systems, caches, or history tracking
- For cleaner, more maintainable collection code
These new interfaces make Java collections more expressive and eliminate many common pain points when working with ordered collections.
Note: Sequenced Collections are a preview feature in Java 21. Make sure to enable preview features (--enable-preview) when compiling and running code that uses these interfaces.