New Java APIs From 9 to 21: List.of, HttpClient, getLast()
Java library additions since Java 9: List.of and Map.of, strip and isBlank, Stream.toList, HttpClient, helpful NullPointerExceptions and sequenced collections.
- Course: Java study plan
- Module: Modern Java
- Kind: Lesson
- Reading time: 14 min
- Runtime: OpenJDK 18
What is the difference between List.of and Arrays.asList?
List.of, added in Java 9, returns a truly immutable list: every mutator throws UnsupportedOperationException and null elements are rejected. Arrays.asList returns a fixed-size list backed by the array — set works and writes through to the array, but add and remove throw. Collections.unmodifiableList is a read-only view of a list that can still change underneath.
Lesson
Language features get the headlines; the library additions save you more lines per day. Since Java 9 the standard library has gained immutable collection factories, a dozen String methods, a modern HTTP client, Stream.toList(), Collectors.teeing, helpful NullPointerException messages and, in 21, the sequenced collections that finally give List a getLast(). This lesson is the tour of the ones you will use weekly, grouped by what they replace, with the version each arrived in so you know what your target JDK has.
Collection factories (9) and copies (10)
List<String> names = List.of("ann", "bob"); // immutable; nulls rejected with NPE
Set<Integer> primes = Set.of(2, 3, 5); // immutable; duplicates throw IllegalArgumentException
Map<String, Integer> ages = Map.of("ann", 30, "bob", 25); // up to 10 pairs
Map<String, Integer> big = Map.ofEntries(Map.entry("a", 1), Map.entry("b", 2));
List<String> frozen = List.copyOf(mutableList); // immutable snapshot; returns the same instance if already immutable
They replace Arrays.asList (fixed-size but mutable via set, and backed by the array) and Collections.unmodifiableList (a live view — the underlying list can still change). The factories are truly immutable, compact (a two-element list is one small object), and null-hostile. Any mutator throws UnsupportedOperationException. Set.of and Map.of iterate in an order that is randomised per JVM run on purpose, so nobody comes to depend on it.
Strings (11, 12, 13, 15)
" x ".strip(); " x".stripLeading(); "x ".stripTrailing(); // Unicode-aware, unlike trim()
" ".isBlank(); // true — whitespace-only
"a\nb\r\nc".lines(); // Stream<String>, any line terminator
"ab".repeat(3); // "ababab"
"line".indent(4); // 12: " line\n" — adds a newline
"hello".transform(String::toUpperCase); // 12: apply a function, for chaining
"""
a
b
""".stripIndent(); // 13: remove common leading whitespace
"%s has %d".formatted("ann", 3); // 15: String.format as an instance method
"héllo".chars(); "😀".codePoints(); // 9: codePoints() on CharSequence
strip versus trim: trim removes characters ≤ U+0020; strip uses Character.isWhitespace, so it also removes Unicode spaces like U+2003. lines() plus strip plus isBlank is the modern way to clean text input; repeat retires the StringBuilder loop for padding.
Streams and collectors (9, 12, 16)
Stream.iterate(1, x -> x < 100, x -> x * 2); // 9: finite iterate
stream.takeWhile(p); stream.dropWhile(p); // 9
Stream.ofNullable(maybeNull); // 9: 0 or 1 elements
Collectors.filtering(p, downstream); Collectors.flatMapping(f, downstream); // 9
Collectors.teeing(c1, c2, merger); // 12
stream.toList(); // 16: unmodifiable, shorter than collect(toList())
stream.mapMulti((x, sink) -> { if (…) sink.accept(…); }); // 16: an imperative flatMap
Predicate.not(String::isBlank); // 11: readable negation of a method reference
Optional (9, 10, 11)
ifPresentOrElse(action, emptyAction), or(supplier) and stream() in 9; orElseThrow() with no arguments in 10 (the honest replacement for get()); isEmpty() in 11. Covered in the streams module — mentioned here so you know which JDK has them.
Objects and helpful NPEs (9, 14)
String name = Objects.requireNonNullElse(input, "anonymous"); // 9
int len = Objects.requireNonNullElseGet(list, ArrayList::new).size(); // 9
Objects.checkIndex(i, size); // 9: throws IndexOutOfBoundsException with a message
Java 14's helpful NullPointerException (on by default since 15) says what was null: Cannot invoke "String.length()" because the return value of "Person.name()" is null — instead of a bare line number. It makes the most common exception in Java self-explanatory; when a local is involved the message names it if the class was compiled with -g, otherwise <local4>.
HttpClient (11)
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.example.com/v1/items"))
.header("Accept", "application/json").timeout(Duration.ofSeconds(5)).GET().build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString()); // blocking
CompletableFuture<HttpResponse<String>> async = client.sendAsync(req, BodyHandlers.ofString()); // non-blocking
HTTP/2, WebSockets, asynchronous via CompletableFuture, immutable request objects, a builder API — everything HttpURLConnection was not. For most services it removes the need for a third-party client.
Files, time and misc (9, 11, 16, 18)
Files.readString/writeString (11), Files.mismatch (12); Path.of (11); InputStream.readAllBytes (9) and transferTo (9) — in.transferTo(out) is the copy loop you never write again; Duration.toSeconds/toDaysPart (9); Math.floorMod overloads; Random's RandomGenerator interface with better algorithms (17: RandomGenerator.of("L64X128MixRandom")); Thread.onSpinWait (9); Process.pid() and ProcessHandle (9); the built-in jwebserver static file server (18); System.Logger (9) as a facade over whatever logging framework is present.
Java 21: sequenced collections and friends
SequencedCollection<E>: getFirst(), getLast(), addFirst(e), addLast(e), removeFirst(), removeLast(), reversed()
list.getLast(); // instead of list.get(list.size() - 1)
list.reversed(); // a live reversed VIEW, not a copy
new LinkedHashSet<>(…).getFirst(); // insertion-ordered sets finally expose their ends
sortedMap.firstEntry(); linkedHashMap.pollLastEntry(); // SequencedMap
List, Deque, LinkedHashSet, SortedSet, LinkedHashMap and SortedMap all gained these; getLast() on an empty collection throws NoSuchElementException. Also in 21: StringBuilder.repeat, Math.clamp, String.indexOf(ch, from, to), Character.isEmoji, and the HexFormat class (17) for the hex dumps you wrote by hand last module.
Interview angle
- "
List.ofversusArrays.asListversusCollections.unmodifiableList?" Immutable & null-hostile; fixed-size mutable view over the array; live read-only view over a list that can still change underneath. - "
stripversustrim?"stripis Unicode-aware (Character.isWhitespace);trimremoves ≤U+0020only. - "What is a helpful NPE?" The Java 14+ message describing exactly which expression was null.
- "How would you call an HTTP API in plain Java?"
java.net.http.HttpClient(11) — syncsendor asyncsendAsync, HTTP/2. - "What did Java 21 add to
List?"getFirst,getLast,addFirst,addLast,removeFirst,removeLast,reversed()viaSequencedCollection.
Key takeaways
List.of/Set.of/Map.of/copyOf: immutable, null-hostile, compact; mutators throw.- Strings:
strip,isBlank,lines,repeat,indent,transform,stripIndent,formatted. - Streams:
toList,takeWhile/dropWhile, finiteiterate,teeing,mapMulti,Predicate.not. Objects.requireNonNullElse; helpful NPE messages;HttpClient;transferTo;readString/writeString.- 21: sequenced collections (
getFirst/getLast/reversed),Math.clamp,HexFormat(17).
Common questions
What is the difference between strip() and trim() in Java?
trim() removes leading and trailing characters up to U+0020, while strip(), added in Java 11, uses Character.isWhitespace and so also removes Unicode spaces such as U+2003. stripLeading() and stripTrailing() remove one side only.
What is a helpful NullPointerException in Java?
Since Java 14, and by default since Java 15, a NullPointerException message says which expression was null — for example, that the return value of Person.name() was null — instead of giving only a line number.
How do you make an HTTP request in plain Java?
Use java.net.http.HttpClient, standard since Java 11: build an HttpRequest with HttpRequest.newBuilder, then call send for a blocking response or sendAsync for a CompletableFuture. It supports HTTP/2 and WebSockets, replacing HttpURLConnection.
What are sequenced collections in Java 21?
SequencedCollection gives ordered collections such as List, Deque and LinkedHashSet common methods: getFirst, getLast, addFirst, addLast, removeFirst, removeLast and reversed(). list.getLast() replaces list.get(list.size() - 1), and reversed() is a live view.
What is the difference between Stream.toList() and Collectors.toList()?
stream.toList(), added in Java 16, is shorter and always returns an unmodifiable list. collect(Collectors.toList()) makes no promise about the list's type or mutability — in practice it returns an ArrayList — so use it only when the result must be modified.
Exercises
Text block invoice
Read a customer name, an integer n, and n items name qty price. Print an invoice built from a text block and formatted:
Invoice for <name>
<a line of - as long as the line above>
<item> x<qty> @ <price, 2 dp> = <line total, 2 dp>
…
Total: <sum, 2 dp>
Use "-".repeat(...) for the rule and Locale.ROOT formatting (String.format(Locale.ROOT, …) for the numbers; formatted uses the default locale, so keep it for the text parts).
Example: Ada, 2, pen 3 1.5, pad 1 4 →
Invoice for Ada
---------------
pen x3 @ 1.50 = 4.50
pad x1 @ 4.00 = 4.00
Total: 8.50The factories say no
Read an integer n and n words, where - stands for null. Then:
- Try
Set.of(words...): printset=ok size=<n>, or onIllegalArgumentExceptionprintset=<the exception message>(it readsduplicate element: x), or onNullPointerExceptionprintset=null rejected. - Build a mutable
ArrayList, thenList.copyOfit (skip if any element is null — printcopy=skipped); trycopy.add("z")and printcopy=immutableif it throwsUnsupportedOperationException. - Print
asList=<result of Arrays.asList(array).set(0, "q") — "set ok">and then try.add("z")on that list, printingasList=fixed-sizewhen it throws.
Example: 3 then a b a →
set=duplicate element: a
copy=immutable
asList=set ok
asList=fixed-sizeIn this module: Modern Java
- The release train — what changed from Java 9 to 21
- var and type inference — what the compiler already knows
- Pattern matching — instanceof, switch and record patterns
- The API additions worth knowing, 9 to 21 (this lesson)
- Virtual threads and structured concurrency
- Modules, jshell and the modern toolchain
- Checkpoint — Modern Java
← Pattern matching — instanceof, switch and record patterns · Virtual threads and structured concurrency →