Sequenced Collections in Java 21: SequencedSet and SequencedMap

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:

  • List had getFirst(), getLast()
  • Deque had getFirst(), getLast(), getFirst(), getLast()
  • SortedSet had first(), last()
  • LinkedHashSet had 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

  1. Consistent API: Unified methods across different collection types
  2. Improved Readability: Clear intent when accessing collection endpoints
  3. Reversible Views: Built-in support for reversed iteration
  4. Reduced Boilerplate: No need for workarounds to access first/last elements
  5. Enhanced Functionality: Additional operations like addFirst, addLast

Implementation Support

SequencedSet implementations:

  • LinkedHashSet
  • TreeSet (read-only for mutation operations)
  • ConcurrentSkipListSet (read-only for mutation operations)

SequencedMap implementations:

  • LinkedHashMap
  • TreeMap (read-only for putFirst/putLast)
  • ConcurrentSkipListMap (read-only for putFirst/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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper