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.