PrepZone Logo
PrepZone

From Java 8 to 17

The Java 11 features you use daily, modules, helpful null messages, and what was removed.

Read these first

Why this matters

  • Most codebases skip from 8 straight to 17, so the intermediate releases arrive all at once and the useful additions are easy to miss.
  • The removals — not the additions — are what actually break a migration, and they are predictable.
  • Helpful NullPointerException messages alone save real debugging time, and they are on by default from 15 onwards.

The release model

Java 8Lambdas, streams
Java 11var, HTTP client
Java 17Records, sealed, switch
Java 21Virtual threads, patterns
Each long-term support release is supported for years, which is why most teams sit on one of these four rather than the six-month interim releases.

Since Java 9 there is a release every six months, with a long-term-support release every two or three years. The LTS versions are 8, 11, 17, 21 and 25; those are the ones production systems target. Non-LTS releases are useful for trying preview features, not for running services on.

The Java 9–11 API additions you will use daily

Java
// Immutable collection factories — Java 9
List<String> list = List.of("a", "b", "c");
Set<String> set = Set.of("a", "b");
Map<String, Integer> map = Map.of("a", 1, "b", 2);
Map<String, Integer> bigger = Map.ofEntries(Map.entry("a", 1), Map.entry("b", 2));

// Defensive copies — Java 10
List<String> snapshot = List.copyOf(mutableList);
Java
// var for local variables — Java 10
var orders = new ArrayList<Order>();                 // type is inferred, still static
var entry = Map.entry("key", 1);                      // genuinely useful for verbose generics
for (var order : orders) { }

// Not allowed: fields, method parameters, return types, or without an initialiser
Java
// String methods — Java 11
"  text  ".strip();                  // Unicode-aware, unlike trim()
"".isBlank();                         // true for whitespace-only
"ab".repeat(3);                       // "ababab"
"a\nb".lines().toList();              // a Stream<String> of lines
Java
// Files convenience — Java 11
String content = Files.readString(path);
Files.writeString(path, content);

// Optional and Stream additions — Java 9 to 11
optional.or(() -> otherOptional);
optional.ifPresentOrElse(this::handle, this::missing);
optional.stream();
stream.takeWhile(p);  stream.dropWhile(p);
Stream.iterate(1, n -> n < 100, n -> n * 2);
Stream.ofNullable(maybeNull);
Collectors.flatMapping(...);  Collectors.filtering(...);

// A standard HTTP client — Java 11, replacing HttpURLConnection
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(URI.create(url)).GET().build();
HttpResponse<String> response = client.send(request, BodyHandlers.ofString());

// Single-file source execution — Java 11
// $ java Hello.java        (no separate javac step)

Helpful NullPointerException messages

Before Java 14, a chained expression gave you a line number and nothing else.

Java
// Old message
Exception in thread "main" java.lang.NullPointerException
    at Example.main(Example.java:12)
Java
order.customer().address().city().length();

Which of the three calls returned null? The old message could not say. The new one does.

Java
Exception in thread "main" java.lang.NullPointerException:
    Cannot invoke "Address.city()" because the return value of "Customer.address()" is null
        at Example.main(Example.java:12)

Introduced in Java 14 behind a flag and on by default since Java 15. It costs nothing until an exception is actually thrown, because the message is computed from the bytecode at throw time.

The module system

Java 9 split the JDK itself into modules and added a way for your own code to declare the same structure.

module app.orders
requires app.paymentsDepends on
exports com.app.orders.apiVisible outside
com.app.orders.internalNot exported — hidden
requires names the dependencies; exports names the packages others may use. Anything not exported stays private to the module even if the class is public.
Java
// module-info.java at the source root
module com.example.orders {
    requires com.example.core;             // dependencies, checked at compile and run time
    requires transitive java.sql;           // consumers get java.sql too

    exports com.example.orders.api;         // public to everyone
    exports com.example.orders.spi to com.example.plugin;   // public to one module

    opens com.example.orders.model;         // reflection allowed — for frameworks

    provides OrderHandler with DefaultOrderHandler;    // a service implementation
    uses PaymentGateway;                                // a service this module consumes
}

What modules actually give you

  • Reliable configuration — missing or duplicate modules fail at startup instead of producing a NoClassDefFoundError later.
  • Strong encapsulation — a package not exported is genuinely inaccessible, even by reflection, unless it is opens.
  • A smaller runtime — jlink builds an image containing only the modules you use, which matters for containers.
  • Clear dependencies — the graph is declared rather than inferred from a flat classpath.
Java
# The migration escape hatch, when a library reflects into JDK internals
--add-opens java.base/java.lang=ALL-UNNAMED
--add-exports java.base/sun.nio.ch=ALL-UNNAMED

# Find the problems before you hit them at runtime
jdeps --jdk-internals --multi-release 17 app.jar

What was removed

This is the list that actually breaks builds.

AspectRemoved or deprecatedWhat to do instead
javax.xml.bind (JAXB)Removed in Java 11Add the Jakarta XML Bind artifacts as dependencies
java.activation, java.corbaRemoved in Java 11External dependencies, or drop CORBA entirely
Nashorn JavaScript engineRemoved in Java 15GraalVM JavaScript
Applets and Java Web StartRemovedNo replacement — rearchitect
SecurityManagerDeprecated for removal in Java 17OS-level and container isolation
finalize()Deprecated for removaltry-with-resources, or Cleaner
CMS garbage collectorRemoved in Java 14G1 (the default), or ZGC
Illegal reflective accessDenied by default from Java 16--add-opens, or upgrade the library
  • javax.xml.bind (JAXB)

    Removed or deprecatedRemoved in Java 11
    What to do insteadAdd the Jakarta XML Bind artifacts as dependencies
  • java.activation, java.corba

    Removed or deprecatedRemoved in Java 11
    What to do insteadExternal dependencies, or drop CORBA entirely
  • Nashorn JavaScript engine

    Removed or deprecatedRemoved in Java 15
    What to do insteadGraalVM JavaScript
  • Applets and Java Web Start

    Removed or deprecatedRemoved
    What to do insteadNo replacement — rearchitect
  • SecurityManager

    Removed or deprecatedDeprecated for removal in Java 17
    What to do insteadOS-level and container isolation
  • finalize()

    Removed or deprecatedDeprecated for removal
    What to do insteadtry-with-resources, or Cleaner
  • CMS garbage collector

    Removed or deprecatedRemoved in Java 14
    What to do insteadG1 (the default), or ZGC
  • Illegal reflective access

    Removed or deprecatedDenied by default from Java 16
    What to do instead--add-opens, or upgrade the library

JAXB and reflective access into JDK internals account for the large majority of real migration failures.

Smaller things worth knowing

Java
// Switch expressions, records, sealed classes, text blocks and pattern matching
// are covered in their own articles — they are the headline language features of 17.

// G1 is the default collector since Java 9; ZGC and Shenandoah are production-ready in 15
// Compact strings (Java 9) store Latin-1 text in one byte per char — ~25% less heap for typical apps
// Application class-data sharing (Java 12+) cuts JVM startup time with no code change
// Unified JVM logging: -Xlog:gc*:file=gc.log replaces the old ad-hoc GC flags
// jshell (Java 9) gives you a REPL for trying code without a class or main method
// Deprecated for removal now: the primitive wrapper constructors — use Integer.valueOf

A practical migration order

The sequence that fails fastest and cheapest

  • Compile on 17, target 8 first — --release 8 — so you find removed APIs without changing runtime behaviour.
  • Run jdeps --jdk-internals against your jars to list every illegal access before it surprises you.
  • Upgrade build tooling and libraries next; most 8-era versions of Maven plugins, bytecode libraries and test frameworks simply do not work on 17.
  • Add the removed JDK modules as dependencies — JAXB is the usual one.
  • Switch the runtime to 17 with the classpath, not modules. Resist modularising at the same time.
  • Only then adopt the new language features, file by file, where they clarify the code.

Common misreadings

  • "Java 17 requires modules." The classpath still works, and most applications use it.
  • "var makes Java dynamically typed." The type is inferred at compile time and fixed.
  • "var works anywhere." Local variables with an initialiser only — not fields, parameters or return types.
  • "List.of is like Arrays.asList." It is unmodifiable and rejects nulls.
  • "Helpful NPE messages slow things down." They are computed only when an exception is thrown.
  • "Every six-month release is production-ready for you." Only LTS releases get extended updates.
  • "--add-opens is a fix." It is a temporary bridge; upgrade the library.
  • "Java 11 removing JAXB means XML is unsupported." JAXB moved out of the JDK into a normal dependency.

Quick recall

Everything you need if you only revisit this box.

  • Six-month releases since Java 9; LTS is 8, 11, 17, 21, 25. Parse versions with Runtime.version(), not by assuming a leading 1..
  • Everyday additions: List.of/Map.of/copyOf, var, strip/isBlank/repeat/lines, Files.readString, HttpClient, Optional.or/ifPresentOrElse/stream, takeWhile/dropWhile.
  • Helpful NullPointerException messages name the exact null expression; default since Java 15.
  • Modules give reliable configuration, strong encapsulation and jlink images — but are optional for your code. The JDK's internals are encapsulated regardless.
  • Removals that break builds: JAXB and java.activation (11), Nashorn (15), CMS (14), applets, and SecurityManager deprecated (17).
  • Free wins with no code change: compact strings, G1 by default, class-data sharing, unified -Xlog logging.
  • Migration order: compile on 17 with --release 8, run jdeps --jdk-internals, upgrade libraries, switch the runtime on the classpath, adopt features last.

Test yourself

Answer these before moving on — recall is what makes it stick.