Java Sequenced Collections (Java 21+)

Java 21 (JEP 431) introduced three new interfaces (SequencedCollection, SequencedSet, and SequencedMap) to provide a uniform API for accessing the first and last elements of ordered collections. Previously, getting the first or last element of a LinkedHashMap or last element of a List required awkward workarounds.

Contents

The Problem Before Java 21

Before Java 21, there was no consistent way to access the first or last element of an ordered collection. Each collection type had its own awkward workaround: get(size()-1) for List, iterating to the end for LinkedHashMap, or calling iterator().next() for LinkedHashSet. None of these were readable, and some were O(n).

Java
// Getting last element of a List — no clean API
List<String> list = List.of("a", "b", "c");

// Before Java 21 — awkward
String last = list.get(list.size() - 1);  // IndexOutOfBoundsException if empty

// Getting first/last of a LinkedHashMap — even worse
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);

// Getting first entry — very awkward
Map.Entry<String, Integer> first = map.entrySet().iterator().next();

// Getting last entry — genuinely painful
Map.Entry<String, Integer> last = null;
for (Map.Entry<String, Integer> e : map.entrySet()) last = e; // O(n)!
// or: ((LinkedHashMap<String,Integer>) map).sequencedEntrySet().last()
// ... which didn't exist before Java 21

// Getting first/last of a LinkedHashSet
LinkedHashSet<String> set = new LinkedHashSet<>(List.of("x","y","z"));
String firstElem = set.iterator().next(); // only way before Java 21

The New Interfaces

  • SequencedCollection<E>: extends Collection<E>: adds getFirst(), getLast(), addFirst(), addLast(), removeFirst(), removeLast(), reversed().
  • SequencedSet<E>: extends SequencedCollection<E> and Set<E>: reversed() returns a SequencedSet.
  • SequencedMap<K,V>: extends Map<K,V>: adds firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry(), putFirst(), putLast(), reversed(), sequencedKeySet(), sequencedValues(), sequencedEntrySet().

SequencedCollection

SequencedCollection is the base interface extended by List, Deque, and LinkedHashSet. It adds getFirst(), getLast(), addFirst(), addLast(), removeFirst(), removeLast(), and reversed(): a reversed view of the same underlying collection (not a copy).

Java
// List implements SequencedCollection
List<String> list = new ArrayList<>(List.of("a", "b", "c", "d"));

// Clean first/last access
String first = list.getFirst(); // "a"
String last  = list.getLast();  // "d"

// Add/remove at ends
list.addFirst("Z");   // ["Z", "a", "b", "c", "d"]
list.addLast("X");    // ["Z", "a", "b", "c", "d", "X"]
list.removeFirst();   // removes "Z"
list.removeLast();    // removes "X"

// reversed() — returns a view in reverse order (not a copy)
List<String> rev = list.reversed();
System.out.println(rev); // [d, c, b, a]
rev.forEach(System.out::println); // iterates in reverse

// Deque also implements SequencedCollection
Deque<Integer> deque = new ArrayDeque<>(List.of(1, 2, 3));
System.out.println(deque.getFirst()); // 1
System.out.println(deque.getLast());  // 3

// LinkedHashSet implements SequencedSet
LinkedHashSet<String> set = new LinkedHashSet<>(List.of("x", "y", "z"));
System.out.println(set.getFirst()); // "x"
System.out.println(set.getLast());  // "z"
SequencedSet<String> revSet = set.reversed();
System.out.println(revSet.getFirst()); // "z"
getFirst() and getLast() throw NoSuchElementException if the collection is empty: unlike the old get(0) which throws IndexOutOfBoundsException.

SequencedMap

SequencedMap extends Map and is implemented by LinkedHashMap, TreeMap, and NavigableMap. It adds firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry(), putFirst(), putLast(), and reversed(), as well as sequencedKeySet(), sequencedValues(), and sequencedEntrySet() for ordered iteration.

Java
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("one",   1);
map.put("two",   2);
map.put("three", 3);

// First and last entries — clean!
Map.Entry<String, Integer> first = map.firstEntry();  // one=1
Map.Entry<String, Integer> last  = map.lastEntry();   // three=3

System.out.println(first.getKey() + "=" + first.getValue()); // one=1
System.out.println(last.getKey()  + "=" + last.getValue());  // three=3

// Poll (retrieve and remove)
Map.Entry<String, Integer> polled = map.pollFirstEntry(); // removes "one"
System.out.println(map.size()); // 2

// putFirst/putLast — insert at beginning/end
map.putFirst("zero", 0); // {zero=0, two=2, three=3}
map.putLast("four", 4);  // {zero=0, two=2, three=3, four=4}

// reversed() — reversed view
SequencedMap<String, Integer> rev = map.reversed();
System.out.println(rev.firstEntry()); // four=4

// Sequenced views — for iteration in insertion order
SequencedCollection<String>              keys   = map.sequencedKeySet();
SequencedCollection<Integer>             values = map.sequencedValues();
SequencedCollection<Map.Entry<String,Integer>> entries = map.sequencedEntrySet();

Which Collections Implement These

  • SequencedCollection: List (ArrayList, LinkedList, Vector, Stack), Deque (ArrayDeque, LinkedList), LinkedHashSet, SortedSet (TreeSet).
  • SequencedSet: LinkedHashSet, SortedSet (TreeSet), NavigableSet.
  • SequencedMap: LinkedHashMap, SortedMap (TreeMap), NavigableMap.
  • Not included: HashSet, HashMap, PriorityQueue. These have no defined encounter order.
Java
// TreeMap (SortedMap → SequencedMap) — natural key order
TreeMap<String, Integer> treeMap = new TreeMap<>();
treeMap.put("banana", 2);
treeMap.put("apple", 1);
treeMap.put("cherry", 3);

System.out.println(treeMap.firstEntry()); // apple=1  (alphabetical)
System.out.println(treeMap.lastEntry());  // cherry=3

// Useful pattern: last-N elements from a sorted map
TreeMap<Long, String> events = new TreeMap<>();
// ... populated with timestamp → event ...
// Get last 3 events:
events.reversed().sequencedEntrySet().stream().limit(3)
    .forEach(e -> System.out.println(e.getKey() + ": " + e.getValue()));