Java · Java 21 Features (18 to 21)

Java 21: Sequenced Collections and the Rest of 18-21

One vocabulary for first, last and reversed across ordered collections - and its surprises - plus UTF-8 as the default charset, the end of finalization, generational ZGC and the preview that was withdrawn.

  • 5 min read
  • 12 practice questions
  • 1 graded problem
On this page
  1. Description
  2. Sequenced collections
  3. The details that surprise people
  4. The rest of Java 18 to 21
  5. String templates: a preview that disappeared
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

Java 21's biggest features (virtual threads, pattern matching) have their own skills. This one covers the rest: an everyday API change called sequenced collections, and the platform changes from Java 18 to 21 that affect how your code runs, from the default text encoding to the newest garbage collector.

Sequenced collections fix a long-standing annoyance. Before Java 21, "get the first and last element" meant something different for every collection:

CollectionFirst elementLast element
Listlist.get(0)list.get(list.size() - 1)
Dequedeque.getFirst()deque.getLast()
SortedSetset.first()set.last()
LinkedHashSetset.iterator().next()walk the whole set
LinkedHashMapmap.entrySet().iterator().next()walk the whole map

Writing a "recently viewed" list, an LRU cache or a pagination cursor meant remembering which of these fit which type. Java 21 gives every collection that has an order the same small set of methods.

Here's the path, from easiest to most technical:

  1. Sequenced collections: one vocabulary for first, last and reversed.
  2. The details that surprise people.
  3. The rest of Java 18 to 21: UTF-8 by default, the end of finalize, a faster garbage collector, small APIs.
  4. String templates: a preview that disappeared.

Sequenced collections

Java 21 added three interfaces to the collection hierarchy:

  • SequencedCollection: getFirst(), getLast(), addFirst(e), addLast(e), removeFirst(), removeLast() and reversed(). Every List, every Deque (ArrayDeque, LinkedList) and the ordered sets have them.
  • SequencedSet: the same, for LinkedHashSet and sorted sets like TreeSet.
  • SequencedMap: firstEntry(), lastEntry(), pollFirstEntry(), pollLastEntry(), putFirst(k, v), putLast(k, v) and reversed(), for LinkedHashMap and sorted maps like TreeMap.
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
list.getFirst();        // "a"
list.getLast();         // "c"
list.reversed();        // [c, b, a]

LinkedHashMap<String, Integer> stock = new LinkedHashMap<>();
stock.put("apples", 3); stock.put("pears", 5);
stock.lastEntry();      // pears=5

HashSet and HashMap have no defined order, so they aren't sequenced: hashMap.firstEntry() doesn't compile.

The details that surprise people

getFirst() on an empty collection throws NoSuchElementException. (get(0) on an empty list throws IndexOutOfBoundsException instead.) Check isEmpty() first when the collection might be empty.

reversed() is a view, not a copy. It's a window that shows the same collection back to front, so changing it changes the original:

List<String> list = new ArrayList<>(List.of("a", "b", "c"));
list.reversed().add("z");    // adds at the END of the reversed view = the START of list
// list is now [z, a, b, c]

Looping over reversed() is free; if you need an independent reversed list, copy it: new ArrayList<>(list.reversed()).

On a LinkedHashSet, addFirst moves an element that's already there:

LinkedHashSet<String> recent = new LinkedHashSet<>(List.of("a", "b", "c"));
recent.addFirst("c");        // [c, a, b]: "c" moved to the front, no duplicate

That's a "recently used" list in a single call. LinkedHashMap.putFirst does the same for keys.

Some collections can't do some operations, and throw UnsupportedOperationException: unmodifiable lists like List.of(1, 2) can't addFirst, and a TreeSet can't either, because its order comes from sorting, not from where you insert.

The rest of Java 18 to 21

UTF-8 by default (Java 18). When code reads or writes text without naming a character encoding (new FileReader(file), new String(bytes), text.getBytes()), Java uses the default charset. That used to depend on the operating system: usually UTF-8 on Linux and macOS servers, but often windows-1252 on Windows. Since Java 18 it's UTF-8 everywhere. Files written with the old default on Windows can read differently after an upgrade. The habit that avoids all of this: always pass the charset explicitly (StandardCharsets.UTF_8) for anything that crosses systems.

finalize() is on its way out (deprecated for removal in Java 18). It runs at an unpredictable time, or never. Close resources with try-with-resources, or register cleanup with java.lang.ref.Cleaner.

Generational ZGC (Java 21). ZGC is a garbage collector that keeps pauses under a millisecond, whatever the heap size. Java 21 added a generational mode (-XX:+UseZGC -XX:+ZGenerational) that handles short-lived objects more cheaply; it became ZGC's default mode in Java 23. G1 remains the default collector.

Small but handy (Java 21): Math.clamp(value, min, max) keeps a number in range, StringBuilder.repeat(...) appends text several times, and Character.isEmoji(...) does what it says. jwebserver (Java 18) is a tiny static file server for testing.

String templates: a preview that disappeared

Java 21 previewed string templates (STR."Hello \{city}"), a way to embed expressions in strings. They stayed a preview in Java 22 and were withdrawn in Java 23 for a redesign, so code written with them on Java 21 doesn't compile on later releases. It's the clearest example of the rule from the Java 17 module: don't put preview features in code you need to keep.

Common mistakes

MistakeWhat happensDo this instead
getFirst() on a possibly empty collectionNoSuchElementExceptioncheck isEmpty() first
Writing to reversed() as if it were a copythe original changescopy it: new ArrayList<>(list.reversed())
Expecting LinkedHashSet.addFirst to add a duplicateit moves the element insteadthat's usually what you want
addFirst on List.of(...) or a TreeSetUnsupportedOperationExceptiona mutable, insertion-ordered collection
firstEntry() on a HashMapdoesn't compileLinkedHashMap or TreeMap
Relying on the platform's default charsettext breaks between systemspass StandardCharsets.UTF_8
Cleanup in finalize()may never runtry-with-resources, or Cleaner

Check yourself

Try to answer before opening each one. Recalling beats re-reading.

1. What does list.reversed().add("z") do to list?

Show answer

reversed() is a view of the same list, so adding at its end adds at the start of the original list.

2. recent is a LinkedHashSet holding [a, b, c]. What is it after recent.addFirst("c")?

Show answer

[c, a, b]. On a LinkedHashSet, addFirst moves an element that's already present to the front instead of adding a duplicate.

3. What changed about new FileReader(file) in Java 18?

Show answer

It reads with the default charset, which is now UTF-8 on every platform instead of following the operating system's settings (often windows-1252 on Windows).

4. Why does hashMap.firstEntry() not compile?

Show answer

A HashMap has no defined order, so it doesn't implement SequencedMap. Use a LinkedHashMap (insertion order) or a TreeMap (sorted order).

Key takeaways

  • Every ordered collection now shares getFirst, getLast, addFirst, addLast, removeFirst, removeLast and reversed(); maps get firstEntry, lastEntry, putFirst and friends.
  • getFirst on an empty collection throws; reversed() is a view; LinkedHashSet.addFirst moves an existing element.
  • HashMap and HashSet aren't sequenced.
  • UTF-8 is the default charset since Java 18, but always pass charsets explicitly.
  • finalize is going away; generational ZGC arrived; string templates were withdrawn.

Quick revise

The whole skill on one page, for the night before an interview.

Everything from the lesson and the deep dive on one page. Skim it before practice or an interview, and open the full lesson for anything that doesn't ring a bell.

Sequenced collections

InterfaceMethodsImplemented by
SequencedCollectiongetFirst/Last, addFirst/Last, removeFirst/Last, reversed()List, Deque
SequencedSetthe same, reversed() returns a setLinkedHashSet, TreeSet
SequencedMapfirstEntry/lastEntry, pollFirst/LastEntry, putFirst/Last, reversed(), sequencedKeySet()...LinkedHashMap, TreeMap

Not sequenced: HashSet, HashMap.

recent.addFirst(item);                 // LinkedHashSet: moves an existing item to the front
if (!recent.isEmpty()) recent.getLast();   // empty → NoSuchElementException
new ArrayList<>(list.reversed());      // reversed() is a VIEW; copy it if you need one

Java 18 to 21

ReleaseChange
18UTF-8 default charset (files, byte conversions); finalize deprecated for removal; jwebserver
21Generational ZGC (default ZGC mode in 23); Math.clamp, StringBuilder.repeat, Character.isEmoji, splitWithDelimiters
21 (preview)string templates, withdrawn in 23; FFM API (final in 22)

The traps

  • getFirst() on empty collections.
  • Writing to reversed() expecting a copy.
  • addFirst on List.of or TreeSet (UnsupportedOperationException).
  • firstEntry() on a HashMap (doesn't compile).
  • Platform-default charsets; finalize for cleanup; string templates in kept code.

Staff-level points, one line each

  • Retrofitting: covariant reversed() (LinkedList returns a List-and-Deque view); custom getFirst() methods can clash on recompile; Android/Kotlin NoSuchMethodError on older runtimes.
  • lastEntry() on LinkedHashMap is O(1) now; reversed() views don't copy.
  • JEP 400 didn't change the console: stdout.encoding follows the locale. With LANG=C, System.out is US-ASCII and emoji print as ? (measured). Use LANG=C.UTF-8 or -Dstdout.encoding=UTF-8.
  • native.encoding shows the pre-18 default; -Dfile.encoding=COMPAT restores it.
  • finalize: unpredictable, can resurrect, needs two GCs. Use try-with-resources; Cleaner as a safety net (action must not capture this; clean() runs once).
  • ZGC: colored pointers + load barriers → concurrent relocation, sub-ms pauses, some throughput and headroom cost; generational mode exploits "most objects die young".

Say it in an interview

  • "A LinkedHashSet with addFirst gives me a recently-used list in one call."
  • "reversed() is a view, so I copy it before handing it out."
  • "The ? in our logs is the container's locale: Java's UTF-8 default doesn't cover stdout."

Practice

This skill comes with 12 multiple-choice questions that tell you which misconception a wrong answer reveals and 1 graded problem. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Java 21: Sequenced Collections and the Rest of 18-21 free

Practice Java 21: Sequenced Collections and the Rest of 18-21 free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime