Java Sequenced Collections: getFirst, getLast and reversed()

Learn Java sequenced collections with examples of getFirst(), getLast(), reversed(), putFirst() and the exceptions and upgrade pitfalls to watch for.

SequencedCollection, SequencedSet and SequencedMap in the Java collections hierarchy with List, Deque, TreeSet, LinkedHashSet, TreeMap and LinkedHashMap

Sequenced collections are the Java 21 interfaces SequencedCollection, SequencedSet and SequencedMap, which give every collection with a defined encounter order the same methods to read, add and remove elements at both ends and to get a reversed view. Encounter order means that one element comes before or after another, which does not have to match the physical position in memory.

We use sequenced collections whenever code needs the first or the last element of a list, a LinkedHashSet or a LinkedHashMap, or needs to walk a collection backwards without copying it. The same calls work on ArrayList, ArrayDeque, TreeSet and TreeMap.

The following example shows the core methods on a list and a map.

List<String> fruits = new ArrayList<>(List.of("apple", "banana", "cherry"));
String first = fruits.getFirst();                                  // "apple"
String last = fruits.getLast();                                    // "cherry"
List<String> backwards = fruits.reversed();                        // [cherry, banana, apple]
SequencedMap<String, Integer> stock = new LinkedHashMap<>();
stock.put("apple", 5);
stock.put("banana", 3);
Map.Entry<String, Integer> lastEntry = stock.lastEntry();          // banana=3

Notice that reversed() returns a view of the same list, not a copy. We cover the three interfaces with their methods, the collection classes that implement them, the exceptions they throw and the problems custom collection classes can hit after an upgrade to Java 21.

1. What are Sequenced Collections?

Sequenced collections came with JEP 431 in Java 21. The JEP adds three interfaces to the collection hierarchy and makes the existing ordered collection classes implement them, so they share one API for the first element, the second element and so on up to the last element.

  • SequencedCollection is the base interface. List and Deque extend it.
  • SequencedSet is a SequencedCollection without duplicates. SortedSet extends it, and LinkedHashSet implements it.
  • SequencedMap is a Map with ordered entries. SortedMap extends it, and LinkedHashMap implements it.
SequencedCollection, SequencedSet and SequencedMap in the Java collections hierarchy with List, Deque, TreeSet, LinkedHashSet, TreeMap and LinkedHashMap
The sequenced interfaces sit between Collection or Map and the ordered collection types; hash-based types stay outside

Only collections with a defined order implement the new interfaces. A HashSet or a HashMap has no stable order, so it has no getFirst() or firstEntry() method.

ClassSequenced typeaddFirst / putFirst
ArrayList, LinkedListList (a SequencedCollection)Inserts at index 0
ArrayDequeDeque (a SequencedCollection)Inserts at the head
LinkedHashSetSequencedSetInserts, or moves an existing element to the front
TreeSetSortedSet (a SequencedSet)Throws UnsupportedOperationException
LinkedHashMapSequencedMapInserts, or moves an existing key to the front
TreeMapSortedMap (a SequencedMap)Throws UnsupportedOperationException
HashSet, HashMapNoneNot available

2. Motivation behind Sequenced Collections

Before Java 21, every ordered collection had its own way to reach its ends, and some had none. A List used index arithmetic, a Deque and a SortedSet had their own methods, and a LinkedHashSet needed a full iteration to find its last element.

List<Integer> list = List.of(1, 2, 3);
Integer oldFirst = list.get(0);                                    // 1
Integer oldLast = list.get(list.size() - 1);                       // 3
LinkedHashSet<Integer> set = new LinkedHashSet<>(list);
Integer oldSetFirst = set.iterator().next();                       // 1
Integer oldSetLast = set.stream().reduce((a, b) -> b).orElseThrow(); // 3

With sequenced collections, the same two method calls work on every ordered collection type.

List<Integer> numbers = List.of(1, 2, 3);
LinkedHashSet<Integer> ordered = new LinkedHashSet<>(numbers);
Integer listFirst = numbers.getFirst();                            // 1
Integer listLast = numbers.getLast();                              // 3
Integer setLast = ordered.getLast();                               // 3

3. The SequencedCollection Interface

The SequencedCollection interface provides methods to add, retrieve and remove elements at either end of the collection, plus reversed(). All methods except reversed() are default methods, and six of them were promoted from Deque.

MethodWhat it doesOn an empty collection
addFirst(E), addLast(E)Adds an element at the front or the backWorks
getFirst(), getLast()Returns the first or the last elementThrows NoSuchElementException
removeFirst(), removeLast()Removes and returns the first or the last elementThrows NoSuchElementException
reversed()Returns a reverse-ordered viewReturns an empty view

The following example creates an ArrayList and uses the sequenced operations on it.

ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.addFirst(0);
arrayList.addLast(2);
String content = arrayList.toString();                             // "[0, 1, 2]"
Integer firstElement = arrayList.getFirst();                       // 0
Integer lastElement = arrayList.getLast();                         // 2
List<Integer> reversed = arrayList.reversed();                     // [2, 1, 0]
arrayList.add(3);
String reversedAfterAdd = reversed.toString();                     // "[3, 2, 1, 0]"
reversed.addFirst(9);
String original = arrayList.toString();                            // "[0, 1, 2, 3, 9]"

Any change to the list is visible in the reversed view, and the view writes through as well. Calling addFirst(9) on the reversed view adds 9 at the end of the original list. For an independent copy, we wrap the view with new ArrayList<>(list.reversed()).

4. The SequencedSet Interface

The SequencedSet interface is for Set implementations with an order, such as LinkedHashSet and TreeSet. It extends SequencedCollection and overrides only reversed(), so that the reversed view is a SequencedSet too.

A set cannot hold duplicates, so addFirst() and addLast() on a LinkedHashSet move an element that is already present to the requested end instead of adding it twice.

LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>(List.of(1, 2, 3));
Integer firstInSet = linkedHashSet.getFirst();                     // 1
Integer lastInSet = linkedHashSet.getLast();                       // 3
linkedHashSet.addFirst(0);
linkedHashSet.addLast(4);
String setContent = linkedHashSet.toString();                      // "[0, 1, 2, 3, 4]"
SequencedSet<Integer> reversedSet = linkedHashSet.reversed();      // [4, 3, 2, 1, 0]
linkedHashSet.addFirst(3);
String moved = linkedHashSet.toString();                           // "[3, 0, 1, 2, 4]"

5. The SequencedMap Interface

The SequencedMap interface is for Map classes with ordered entries, such as LinkedHashMap and TreeMap. It does not extend SequencedCollection, because a map holds entries, not elements, so it has its own methods for the first and last entry.

MethodWhat it does
firstEntry(), lastEntry()Returns the first or the last entry, or null if the map is empty
pollFirstEntry(), pollLastEntry()Removes and returns the first or the last entry, or null
putFirst(K, V), putLast(K, V)Inserts the entry at the front or the back, or moves an existing key there
reversed()Returns a reverse-ordered view of the map
sequencedKeySet(), sequencedValues(), sequencedEntrySet()Returns the keys, values or entries as sequenced views

The methods firstEntry(), lastEntry(), pollFirstEntry() and pollLastEntry() were promoted from NavigableMap, and they return null on an empty map instead of throwing.

LinkedHashMap<Integer, String> map = new LinkedHashMap<>();
map.put(1, "One");
map.put(2, "Two");
map.put(3, "Three");
Map.Entry<Integer, String> firstMapEntry = map.firstEntry();       // 1=One
Map.Entry<Integer, String> lastMapEntry = map.lastEntry();         // 3=Three
Map.Entry<Integer, String> polledFirst = map.pollFirstEntry();     // 1=One
Map.Entry<Integer, String> polledLast = map.pollLastEntry();       // 3=Three
String afterPoll = map.toString();                                 // "{2=Two}"
map.putFirst(1, "One");
map.putLast(3, "Three");
String restored = map.toString();                                  // "{1=One, 2=Two, 3=Three}"
SequencedMap<Integer, String> reversedMap = map.reversed();        // {3=Three, 2=Two, 1=One}
SequencedSet<Integer> keysBackwards = map.sequencedKeySet().reversed(); // [3, 2, 1]

The views from keySet(), values() and entrySet() keep the same order as the sequenced views. The difference is the return type, because only sequencedKeySet() and its siblings return a type with getFirst() and reversed().

6. New Methods Added in Collections Class

The Collections utility class has three new methods that return unmodifiable views of the new types, in the same style as Collections.unmodifiableList(). An unmodifiable view rejects changes through itself, but it still shows the changes that the owner makes to the backing collection.

LinkedHashMap<String, Integer> scores = new LinkedHashMap<>();
scores.put("anna", 7);
SequencedMap<String, Integer> readOnly = Collections.unmodifiableSequencedMap(scores);
scores.put("lokesh", 9);
Map.Entry<String, Integer> newest = readOnly.lastEntry();          // lokesh=9
Integer rejected = readOnly.putFirst("bob", 1);                    // UnsupportedOperationException

The other two methods are Collections.unmodifiableSequencedCollection() and Collections.unmodifiableSequencedSet(). For a copy that never changes, we use List.copyOf() or the immutable factory methods.

7. Watch out for UnsupportedOperationException

The add and remove methods of the new interfaces are optional operations. If we call them on an unmodifiable collection, we get an UnsupportedOperationException, although the read methods and reversed() still work.

List<Integer> fixed = List.of(1, 2, 3);
List<Integer> fixedReversed = fixed.reversed();                    // [3, 2, 1]
fixed.addLast(4);                                                  // UnsupportedOperationException

Sorted collections also reject addFirst() and addLast(), because their order comes from the comparator and not from the position where we insert. In a TreeSet, we call add() and let the comparator place the element.

TreeSet<Integer> sorted = new TreeSet<>(List.of(1, 2, 3));
Integer smallest = sorted.getFirst();                              // 1
sorted.addFirst(4);                                                // UnsupportedOperationException

8. NoSuchElementException on Empty Collections

The methods getFirst(), getLast(), removeFirst() and removeLast() throw NoSuchElementException on an empty collection, because there is no element to return. When the collection can be empty, we check isEmpty() first or use a stream with findFirst().

List<Integer> empty = new ArrayList<>();
Integer missing = empty.getFirst();                                // NoSuchElementException
Integer safe = empty.isEmpty() ? null : empty.getFirst();          // null
Optional<Integer> firstOrEmpty = empty.stream().findFirst();       // Optional.empty

9. Pitfalls

The sequenced collection changes fit into the collections framework without breaking code that only uses the JDK collection classes. Custom collection classes are a different story, and two problems can appear after an upgrade to Java 21.

The first problem is a name clash. If a custom collection already has a method such as getFirst() or reversed() with a different return type, it no longer compiles, so we rename the method or make its return type match the new interface.

The second problem hits classes that implement both List and Deque. Both interfaces declare reversed() with their own return type, and List has default versions of methods that are abstract in Deque, so the class does not compile.

// does not compile: Deque and List both define reversed() with unrelated return types
abstract class TrackQueue<E> extends AbstractList<E> implements Deque<E> {
}

The fix is to override reversed() with a return type that is both a List and a Deque, which is the class itself, and to override the six methods that both interfaces declare. LinkedList does the same in the JDK, as its reversed() method returns a LinkedList.

abstract class TrackQueue<E> extends AbstractList<E> implements Deque<E> {
    @Override public abstract TrackQueue<E> reversed();
    @Override public abstract void addFirst(E e);
    @Override public abstract void addLast(E e);
    @Override public abstract E getFirst();
    @Override public abstract E getLast();
    @Override public abstract E removeFirst();
    @Override public abstract E removeLast();
}

10. Recent Searches with a LinkedHashSet

A shopping app shows the five most recent searches of a user, newest first, without duplicates. When the user searches for a term again, it moves to the top. A LinkedHashSet with addFirst() handles the move, and removeLast() drops the oldest term when the list grows too long.

static void remember(LinkedHashSet<String> recent, String term, int limit) {
    recent.addFirst(term);
    if (recent.size() > limit) {
        recent.removeLast();
    }
}
LinkedHashSet<String> recent = new LinkedHashSet<>();
remember(recent, "shoes", 3);
remember(recent, "socks", 3);
remember(recent, "hat", 3);
remember(recent, "shoes", 3);
String afterRepeat = recent.toString();                            // "[shoes, hat, socks]"
remember(recent, "belt", 3);
String afterLimit = recent.toString();                             // "[belt, shoes, hat]"

Before Java 21, the same logic needed a remove() plus a rebuild of the set or a LinkedList with a manual duplicate check. With a sequenced set, the order rules live in the collection and the method has four lines.

11. Sequenced Collections FAQs

Hash-based types, views and duplicate handling are where the new methods behave differently from what code written for older Java versions expects.

11.1. Does HashMap implement SequencedMap?

No. A HashMap has no defined iteration order, so it implements neither SequencedMap nor SequencedCollection. When we need first and last entries, we use a LinkedHashMap for insertion order or a TreeMap for sorted order.

11.2. Is reversed() a copy of the collection?

No. The reversed() method returns a view, so it takes constant time and memory, and changes in either direction are visible in both. We copy it with new ArrayList<>(list.reversed()) when we need a separate list.

11.3. How do we get the last element of a list before Java 21?

We call list.get(list.size() – 1) after checking that the list is not empty. On Java 21 and later, list.getLast() does the same and throws NoSuchElementException on an empty list instead of IndexOutOfBoundsException.

11.4. Does addFirst() on a LinkedHashSet create a duplicate?

No. If the element is already in the set, addFirst() moves it to the front, and addLast() moves it to the back. The same rule applies to putFirst() and putLast() on a LinkedHashMap.

11.5. Can we use sequenced methods on a stream?

No. Streams have their own methods such as findFirst(), while the sequenced methods belong to collections. The streams guide covers ordered stream operations.

12. Conclusion

The sequenced collection interfaces give List, Deque, LinkedHashSet, LinkedHashMap, TreeSet and TreeMap one API for both ends and for reverse order. Most Java collections already had an encounter order through their iterators, and Java 21 made it part of the type system. The Java features by version page shows where the feature fits among the other changes.

We keep a few rules in mind. The reversed() method returns a view, sets and linked maps move existing elements instead of duplicating them, sorted and unmodifiable collections throw UnsupportedOperationException for the add methods, and empty collections throw NoSuchElementException for the get and remove methods.

13. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.