Java 21 Sequenced Collections

August 1, 20264 min readUpdated 8/20/2026

Java 21 added three interfaces — SequencedCollection, SequencedSet and SequencedMap — giving every collection with a defined order the same way to ask for its first and last element, and to iterate backwards. It is a small addition that fixes a twenty-five-year-old inconsistency.

The inconsistency

Before Java 21, getting the first element depended entirely on which collection you had:

class Demo {
    void run() {
        List<String> list = new ArrayList<>(List.of("a", "b", "c"));
        Deque<String> deque = new ArrayDeque<>(List.of("a", "b", "c"));
        SortedSet<String> sorted = new TreeSet<>(List.of("a", "b", "c"));
        LinkedHashSet<String> linked = new LinkedHashSet<>(List.of("a", "b", "c"));

        System.out.println(list.get(0));           // an index
        System.out.println(deque.getFirst());      // a method
        System.out.println(sorted.first());        // a differently named method
        System.out.println(linked.iterator().next());   // no method at all
    }
}

Four ordered collections, four different answers — and the last one is the telling case. A LinkedHashSet has a perfectly well-defined first element and no way to ask for it. The last element was worse: iterating the entire set to find it.

The new interfaces

class Demo {
    void run() {
        List<String> list = new ArrayList<>(List.of("a", "b", "c"));

        System.out.println(list.getFirst());       // a
        System.out.println(list.getLast());        // c
        System.out.println(list.reversed());       // [c, b, a]

        list.addFirst("start");
        list.addLast("end");
        System.out.println(list);                  // [start, a, b, c, end]

        System.out.println(list.removeFirst());    // start
        System.out.println(list.removeLast());     // end
    }
}

Six methods, on everything ordered. List, Deque, LinkedHashSet, SortedSet and LinkedHashMap all gained them without any change to your code — they are default methods, which is precisely the mechanism that makes adding to an interface possible.

The hierarchy

InterfaceExtendsImplemented by
SequencedCollection<E>CollectionList, Deque
SequencedSet<E>SequencedCollection, SetLinkedHashSet, SortedSet
SequencedMap<K,V>MapLinkedHashMap, SortedMap

The useful consequence is that you can now write a method taking any ordered collection:

class Demo {
    <E> E newest(SequencedCollection<E> items) {
        return items.getLast();               // works for List, Deque, LinkedHashSet, TreeSet
    }

    void run() {
        System.out.println(newest(List.of("a", "b")));                    // b
        System.out.println(newest(new LinkedHashSet<>(List.of("a", "b")))); // b
    }
}

reversed() is a view, not a copy

The detail worth knowing. reversed() returns a view backed by the original, so it costs nothing to create and it sees later changes:

class Demo {
    void run() {
        List<String> list = new ArrayList<>(List.of("a", "b"));
        List<String> view = list.reversed();

        list.add("c");
        System.out.println(view);              // [c, b, a] — the view saw the addition

        view.set(0, "z");                      // writing through the view
        System.out.println(list);              // [a, b, z]

        // For an independent copy, materialise it
        List<String> copy = new ArrayList<>(list.reversed());
        list.add("d");
        System.out.println(copy.size());       // 3 — unaffected
    }
}

Compare with Collections.reverse(list), which reverses in place and returns nothing. The view is usually what you want for iterating backwards, and it does not disturb the original.

Maps

class Demo {
    void run() {
        LinkedHashMap<String, Integer> scores = new LinkedHashMap<>();
        scores.put("Ana", 30);
        scores.put("Bo", 25);
        scores.put("Cy", 35);

        System.out.println(scores.firstEntry());     // Ana=30
        System.out.println(scores.lastEntry());      // Cy=35
        System.out.println(scores.reversed());       // {Cy=35, Bo=25, Ana=30}

        scores.putFirst("Zed", 10);                  // insert at the front
        System.out.println(scores.sequencedKeySet().getFirst());   // Zed
    }
}

sequencedKeySet, sequencedValues and sequencedEntrySet return ordered views, which is what lets you get the first key without iterating.

What "sequenced" means, and what it does not

The word is doing precise work here, and it is worth separating three ideas that get conflated.

A collection is sequenced if it has a well-defined encounter order — there is a first element, a last element, and iterating twice gives the same sequence. That is a weaker property than being sorted, and a stronger one than being merely iterable.

class Demo {
    void run() {
        // Sequenced by insertion — order is what you put in
        SequencedSet<String> insertion = new LinkedHashSet<>(List.of("c", "a", "b"));
        System.out.println(insertion.getFirst());     // c

        // Sequenced by sorting — order is the comparator's
        SequencedSet<String> sorted = new TreeSet<>(List.of("c", "a", "b"));
        System.out.println(sorted.getFirst());        // a

        // NOT sequenced — HashSet has no defined order, so it gains nothing
        Set<String> unordered = new HashSet<>(List.of("c", "a", "b"));
        // unordered.getFirst();                      // does not compile
    }
}

That last case is the point of the design. HashSet deliberately does not implement SequencedSet, because its iteration order is an implementation detail that can change between runs. Asking a HashSet for its "first" element is a meaningless question, and the type system now says so rather than letting you write hashSet.iterator().next() and believe the answer.

The same reasoning applies to HashMap: it is a Map, not a SequencedMap. If you need order, that is what LinkedHashMap and TreeMap are for — the point the Collections post makes about choosing an implementation deliberately.

One thing that can break

These are new methods on very old interfaces, so a class of yours that implements List and already had its own getFirst() with different semantics now conflicts with the interface's. It is rare, and the compiler tells you, but it is the one upgrade hazard here.

There is also a semantic clash worth noting: Deque already had addFirst and addLast, and its reversed() had to be defined consistently with descendingIterator(). If you use Deque heavily, check that the two agree in your code rather than assuming.

Why it matters

None of this was impossible before — list.get(list.size() - 1) works, everyone wrote it, and everyone got the off-by-one wrong at least once. The value is in removing four different spellings of one idea, and in making LinkedHashSet and LinkedHashMap usable for the thing people actually use them for: keeping insertion order and then looking at the ends of it.

Next

Unnamed variables and patterns is next — a use for the underscore.