AI InterviewSearch…Search problems, topics, or pages…Ctrl KGet started
Skip to content

19 Modern Java / Lesson 4 of 6

The API additions worth knowing, 9 to 21

14 min · +10 XP · 2 exercises · 6 questions

Read the text14 min
Exercises0/2
Quiz6 q

Quick answer

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.

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.of versus Arrays.asList versus Collections.unmodifiableList?" Immutable & null-hostile; fixed-size mutable view over the array; live read-only view over a list that can still change underneath.
  • "strip versus trim?" strip is Unicode-aware (Character.isWhitespace); trim removes ≤ U+0020 only.
  • "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) — sync send or async sendAsync, HTTP/2.
  • "What did Java 21 add to List?" getFirst, getLast, addFirst, addLast, removeFirst, removeLast, reversed() via SequencedCollection.

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, finite iterate, 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.

Mark as readSign in to keep your place.

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.of versus Arrays.asList versus Collections.unmodifiableList?" Immutable & null-hostile; fixed-size mutable view over the array; live read-only view over a list that can still change underneath.
  • "strip versus trim?" strip is Unicode-aware (Character.isWhitespace); trim removes ≤ U+0020 only.
  • "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) — sync send or async sendAsync, HTTP/2.
  • "What did Java 21 add to List?" getFirst, getLast, addFirst, addLast, removeFirst, removeLast, reversed() via SequencedCollection.

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, finite iterate, 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.50

The factories say no

Read an integer n and n words, where - stands for null. Then:

  1. Try Set.of(words...): print set=ok size=<n>, or on IllegalArgumentException print set=<the exception message> (it reads duplicate element: x), or on NullPointerException print set=null rejected.
  2. Build a mutable ArrayList, then List.copyOf it (skip if any element is null — print copy=skipped); try copy.add("z") and print copy=immutable if it throws UnsupportedOperationException.
  3. Print asList=<result of Arrays.asList(array).set(0, "q") — "set ok"> and then try .add("z") on that list, printing asList=fixed-size when it throws.

Example: 3 then a b a →

set=duplicate element: a
copy=immutable
asList=set ok
asList=fixed-size

In this module: Modern Java

← Pattern matching — instanceof, switch and record patterns · Virtual threads and structured concurrency →