On this page
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:
| Collection | First element | Last element |
|---|---|---|
List | list.get(0) | list.get(list.size() - 1) |
Deque | deque.getFirst() | deque.getLast() |
SortedSet | set.first() | set.last() |
LinkedHashSet | set.iterator().next() | walk the whole set |
LinkedHashMap | map.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:
- Sequenced collections: one vocabulary for first, last and reversed.
- The details that surprise people.
- The rest of Java 18 to 21: UTF-8 by default, the end of
finalize, a faster garbage collector, small APIs. - 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()andreversed(). EveryList, everyDeque(ArrayDeque,LinkedList) and the ordered sets have them.SequencedSet: the same, forLinkedHashSetand sorted sets likeTreeSet.SequencedMap:firstEntry(),lastEntry(),pollFirstEntry(),pollLastEntry(),putFirst(k, v),putLast(k, v)andreversed(), forLinkedHashMapand sorted maps likeTreeMap.
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
| Mistake | What happens | Do this instead |
|---|---|---|
getFirst() on a possibly empty collection | NoSuchElementException | check isEmpty() first |
Writing to reversed() as if it were a copy | the original changes | copy it: new ArrayList<>(list.reversed()) |
Expecting LinkedHashSet.addFirst to add a duplicate | it moves the element instead | that's usually what you want |
addFirst on List.of(...) or a TreeSet | UnsupportedOperationException | a mutable, insertion-ordered collection |
firstEntry() on a HashMap | doesn't compile | LinkedHashMap or TreeMap |
| Relying on the platform's default charset | text breaks between systems | pass StandardCharsets.UTF_8 |
Cleanup in finalize() | may never run | try-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,removeLastandreversed(); maps getfirstEntry,lastEntry,putFirstand friends. getFirston an empty collection throws;reversed()is a view;LinkedHashSet.addFirstmoves an existing element.HashMapandHashSetaren't sequenced.- UTF-8 is the default charset since Java 18, but always pass charsets explicitly.
finalizeis going away; generational ZGC arrived; string templates were withdrawn.