Topic 11.6
What Changed from Java 8 to Java 25
In one line
Java ships a new version every six months, and every two years one becomes a long-term support (LTS) release: 8, 11, 17, 21 and 25. This topic maps the important language, library, JVM and removal changes in each release from Java 8 to Java 25, so you can read any codebase, know what version a feature needs, and plan an upgrade.
Think of it like this
Phone software updates. Your phone gets small updates often, and once in a while a big one that the maker promises to support with security fixes for years. Most companies install only the big, long-supported ones, but every small update adds something those big ones will include. Java works the same way: a feature release every six months, and an LTS release that companies standardise on.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Feature release
- A Java version that can add new features. Since Java 10 there is one every six months, in March and September.
- LTS (long-term support)
- A release that vendors keep fixing for many years. The LTS releases are 8, 11, 17, 21 and 25.
- JEP
- JDK Enhancement Proposal: a numbered public document describing one change to Java, like JEP 444 for virtual threads.
- Preview feature
- A finished language feature that is switched off unless you pass
--enable-preview. It may change before becoming standard. - Incubator module
- An early, experimental API shipped in a module named
jdk.incubator.*, for feedback before it is finalised. - Deprecated for removal
- Marked
@Deprecated(forRemoval = true): still present, but scheduled to be deleted in a future release. - Garbage collector (GC)
- The part of the JVM that finds objects nothing points to any more and frees their memory (Topic 14.3).
- OpenJDK
- The open-source project that develops Java. Vendors build and distribute JDKs from its code.
Step by step
01The timeline at a glance
Java 8 came in March 2014 and Java 9 in September 2017. Since then: March and September every year. The LTS releases are the ones most teams adopt, and the ones this map treats as milestones.
Check which JDK you are running with java -version. The first number after version is the feature release. (Up to Java 8 the version string started with 1., like 1.8.0_231; from Java 9 it is just 9, 11.0.2, 21.0.9.)
02Java 8 (March 2014, LTS): the functional revolution
Language: lambda expressions and method references (Topic 10.1, 10.3); functional interfaces like Function and Predicate in java.util.function; default and static methods in interfaces (Topic 5.8); type annotations and repeatable annotations.
Library: the Stream API (Topic 10.4); Optional (Topic 10.7); the new date and time API java.time (Topic 12.5); CompletableFuture (Topic 13.7); String.join; java.util.Base64; StampedLock; Arrays.parallelSort; new Map methods such as getOrDefault, computeIfAbsent, merge and forEach.
JVM: the permanent generation (PermGen) was replaced by Metaspace in native memory, ending most OutOfMemoryError: PermGen space errors. The Nashorn JavaScript engine replaced Rhino.
03Java 9 (September 2017) and Java 10 (March 2018)
Java 9. The module system (JPMS, Project Jigsaw, Topic 15.3): the JDK itself was split into modules like java.base. JShell, the interactive shell (Topic 0.6). Collection factory methods List.of, Set.of, Map.of (Topic 9.11). Private methods in interfaces. Try-with-resources on an existing effectively-final variable. The diamond <> with anonymous classes. Stream additions takeWhile, dropWhile, ofNullable and a three-argument iterate; Optional.ifPresentOrElse, or and stream. G1 became the default garbage collector. Compact strings store Latin-1 text in one byte per character. Also StackWalker, VarHandle, the Flow reactive-streams interfaces, multi-release JARs, and _ became a keyword.
Java 10. Local variable type inference with **var** (Topic 1.11). List.copyOf, Set.copyOf, Map.copyOf; Collectors.toUnmodifiableList and friends; Optional.orElseThrow() with no arguments. Application class-data sharing, and the JVM began respecting container (Docker) CPU and memory limits. Java 10 was the first six-month release.
04Java 11 (September 2018, LTS): the modern baseline
Language: var allowed in lambda parameters, so they can carry annotations: (@NonNull var x) -> .... Running a single source file directly: java Hello.java (JEP 330).
Library: the standard HTTP client java.net.http.HttpClient (HTTP/1.1 and HTTP/2, sync and async). String.isBlank, strip, stripLeading, stripTrailing, lines and repeat (Topic 6.4). Files.readString and Files.writeString. Optional.isEmpty. Predicate.not. Collection.toArray(IntFunction). TLS 1.3.
JVM and tools: Flight Recorder (JFR) open-sourced into OpenJDK (Topic 14.5); the no-op Epsilon GC; ZGC as an experimental low-pause collector.
Removed: the Java EE and CORBA modules (JEP 320): javax.xml.bind (JAXB), javax.xml.ws (JAX-WS), javax.activation, javax.annotation and others now come from separate libraries. JavaFX is no longer bundled with Oracle's JDK. Nashorn was deprecated.
05Java 12 to 16: less boilerplate
Java 12 (2019): String.indent and String.transform; Collectors.teeing; Files.mismatch; CompactNumberFormat; the experimental Shenandoah GC; switch expressions as a preview.
Java 13 (2019): text blocks and yield in switch expressions as previews; ZGC can return unused memory to the operating system; dynamic class-data-sharing archives.
Java 14 (2020): switch expressions become standard (Topic 2.4). Helpful NullPointerExceptions that name the null variable (Topic 7.8). Records and pattern matching for instanceof as previews. The CMS garbage collector was removed; JFR event streaming added.
Java 15 (2020): text blocks become standard (Topic 6.5), along with String.formatted and stripIndent. ZGC and Shenandoah become production features. Hidden classes for frameworks. The EdDSA signature algorithm. Nashorn removed. Biased locking deprecated. Sealed classes as a preview.
Java 16 (2021): records (Topic 4.9) and **pattern matching for instanceof** (Topic 11.1) become standard. Stream.toList(). The jpackage tool for native installers. Unix-domain socket channels. JDK internals strongly encapsulated by default (--illegal-access=deny). OpenJDK moved to Git and GitHub.
06Java 17 (September 2021, LTS): sealed types and locked internals
Language: sealed classes and interfaces become standard (Topic 5.11). Floating-point arithmetic is always strict again, so strictfp is redundant. Pattern matching for switch appears as a preview.
Library: enhanced pseudo-random number generators (RandomGenerator interface and new algorithms); HexFormat; context-specific deserialization filters.
Restrictions and removals: JDK internals are strongly encapsulated with no command-line escape hatch: --illegal-access is gone, and reflective access to internal classes needs explicit --add-opens. The Security Manager and the Applet API are deprecated for removal. RMI activation and the experimental AOT/Graal JIT compiler were removed. A macOS/AArch64 (Apple silicon) port was added.
07Java 18 to 20: UTF-8 everywhere and previews of the future
Java 18 (2022): UTF-8 by default (JEP 400): reading and writing files without an explicit charset now uses UTF-8 on every operating system. A minimal static web server, jwebserver. @snippet for code in Javadoc. Core reflection reimplemented on method handles. Finalization deprecated for removal.
Java 19 (2022): previews of virtual threads, record patterns and the foreign function and memory API; structured concurrency as an incubator; a Linux/RISC-V port.
Java 20 (2023): further previews of the same features, plus scoped values as an incubator. Nothing major became standard in 19 or 20, but these previews shaped Java 21.
08Java 21 (September 2023, LTS): virtual threads and data-oriented Java
Language: record patterns (Topic 11.3) and **pattern matching for switch** (Topic 11.2) become standard.
Library: virtual threads (JEP 444, Topic 13.10): millions of cheap threads for blocking code. Sequenced collections (JEP 431, Topic 9.12): getFirst, getLast, addFirst, reversed on lists, deques, LinkedHashSet, LinkedHashMap and sorted collections. The key encapsulation mechanism (KEM) API. Small additions such as Math.clamp and StringBuilder.repeat.
JVM: generational ZGC (JEP 439). Warnings when an agent is loaded dynamically into a running JVM. The 32-bit Windows x86 port deprecated for removal.
Previews in 21: unnamed patterns and variables, unnamed classes and instance main methods, string templates (later withdrawn and never released as a standard feature), structured concurrency, and scoped values.
09Java 22 to 25: finishing the picture
Java 22 (2024): unnamed variables and patterns _ (Topic 11.4). The foreign function and memory API (java.lang.foreign, JEP 454) becomes standard, a safer replacement for JNI. Launching multi-file source programs with java Main.java. Region pinning for G1. Previews: statements before super(...), stream gatherers, the class-file API.
Java 23 (2024): Markdown documentation comments (///). ZGC uses generational mode by default. The memory-access methods of sun.misc.Unsafe deprecated for removal. Previews: primitive types in patterns, module import declarations, and more rounds of earlier previews.
Java 24 (2025): stream gatherers (Stream.gather, Gatherers.windowFixed and others, JEP 485) and the class-file API (JEP 484) become standard. Virtual threads no longer pin their carrier thread inside synchronized (JEP 491). Ahead-of-time class loading and linking (JEP 483) for faster start-up. Post-quantum ML-KEM and ML-DSA algorithms. The Security Manager is permanently disabled (JEP 486). Non-generational ZGC removed. Compact object headers as an experimental option.
Java 25 (September 2025, LTS): **compact source files and instance main methods (Topic 11.5). Module import declarations** (import module java.base;). Flexible constructor bodies: statements before super(...) or this(...) as long as they don't use the object being built. Scoped values (Topic 13.11). The key derivation function API. Compact object headers and generational Shenandoah become product features. AOT command-line ergonomics and method profiling for faster warm-up. New JFR features (CPU-time profiling as experimental, method timing and tracing). The 32-bit x86 port removed. Still in preview: structured concurrency, primitive types in patterns, stable values, PEM encodings; the Vector API is still incubating.
// Java 25: flexible constructor bodies (JEP 513)
class Account extends Base {
Account(String id) {
if (id.isBlank()) throw new IllegalArgumentException("blank id"); // before super(...)
super(id.strip());
}
}
// Java 24: stream gatherers (JEP 485)
List<List<Integer>> windows = Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowFixed(2))
.toList(); // [[1, 2], [3, 4], [5]]Try it yourself
- 1
Find the minimum version
Look at the first runnable example and list every feature in the modern half with the version it needs. The newest one sets the minimum Java version for the file. Check your answer against its
sincetag. - 2
Downgrade on purpose
Copy the Java 21 example into a local file and compile it with
javac --release 17 Main.java. Predict which lines fail, then read the errors. Each one is a feature from the Java 21 step above. - 3
Check your own JDK
Run
java -versionandjavac -versionon your machine. Are they the same release? Then runjava -XshowSettings:properties -versionand findjava.versionandjava.vendorin the output.
Code & diagrams
Expected output
Java 8: [chai: few, samosa: many]
Java 16: [chai: few, samosa: many]
Receipt
items: 2
first order: Order[item=chai, qty=2]Stream.toList() used on the non-blank line is Java 16.
Expected output
isBlank: true
strip: [chai]
repeat: ababab
lines: 3
non-blank: [tea, bun]
Optional.empty().isEmpty(): true
transform: CHAI!
teeing average: 25.0
formatted: samosa has 6 letters
hex: 0a1fffExpected output
first: Asha, last: Meera
reversed view: [Meera, Ravi, Asha]
after addFirst: [Kabir, Asha, Ravi, Meera]
first entry: chai=20, last entry: dosa=80
Math.clamp(150, 0, 100) = 100
StringBuilder.repeat: ababab
circle r=1.0
square side=2.0A compact source file (25) using unnamed variables (22) and stream gatherers (24). Run with java Main.java on JDK 25.
void main() {
var scores = List.of(7, 9, 4, 10, 6); // List: auto-imported in a compact file
var pairs = scores.stream()
.gather(Gatherers.windowFixed(2)) // Java 24: stream gatherers
.toList();
IO.println(pairs); // [[7, 9], [4, 10], [6]]
int count = 0;
for (var _ : scores) { // Java 22: unnamed variable
count++;
}
IO.println("count = " + count); // count = 5
}Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use a Java 21 API with --release 17
Compile code that calls queue.getFirst() on a List with javac --release 17 Main.java.
Break #2
Run new bytecode on an old JVM
Compile with JDK 21 (default target 21) and run the class on a Java 8 runtime.
Myth vs fact
Myth
Java 8 is still the version everyone uses.
Fact
Many legacy systems still run it, but 11, 17 and 21 are widely used in production, and frameworks have moved on: Spring Framework 6 and Spring Boot 3 require Java 17 or later.
Myth
Non-LTS releases are betas.
Fact
Every feature release is a production-quality, fully tested JDK. LTS only means vendors keep releasing fixes for it for longer.
Myth
Upgrading Java is only about new syntax.
Fact
The hard parts are removals and restrictions: Java EE modules (11), Nashorn (15), strong encapsulation of internals (16/17), UTF-8 default (18), Security Manager (24). New syntax is optional; removals aren't.
Myth
Preview features are safe to ship in production code.
Fact
They can change or be withdrawn (string templates were previewed in 21 and 22, then removed). Code using them only runs with --enable-preview on that exact release.
Interview problem
The problem
Plan an upgrade from Java 8 to Java 21
Your team runs a Spring Boot 2 service on Java 8. Management wants it on Java 21 for virtual threads and support. How do you plan and de-risk the upgrade?
You're given
- No more than a few minutes of downtime.
- The service uses JAXB for XML, an old Lombok version, and reads CSV files on Windows servers.
The interviewer follows up
Why not jump straight from 8 to 21 in one step?
Will virtual threads speed up the service automatically?
When it breaks
JAXB missing after moving to Java 11
What you see
The service compiles against an old JDK but fails at start-up or on the first XML call with java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException.
Fix & prevent
Add the JAXB API and implementation as explicit dependencies (jakarta.xml.bind-api plus an implementation such as Glassfish JAXB). Do the same for JAX-WS, javax.annotation and other removed Java EE modules.
Reflection into JDK internals breaks on Java 17
What you see
An old library throws java.lang.reflect.InaccessibleObjectException: Unable to make field ... accessible: module java.base does not "opens" ... at start-up. Under Java 9 to 15 it only printed an "illegal reflective access" warning, so the problem went unnoticed.
Fix & prevent
Upgrade the library to a version that supports the target JDK. As a short-term workaround, add the specific --add-opens java.base/<package>=ALL-UNNAMED named in the message, and track its removal.
Garbled text after moving to Java 18+ on Windows
What you see
Files written by other Windows tools in the system code page are read with UTF-8, so accented or non-Latin characters turn into replacement characters, and generated reports differ from before.
Fix & prevent
Always pass an explicit Charset when reading and writing text. For a temporary rollback of the default, start the JVM with -Dfile.encoding=COMPAT.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Class-file major versions are feature release + 44: Java 8 is 52, 11 is 55, 17 is 61, 21 is 65 and 25 is 69.
javap -v Foo.class | grep majortells you which JDK a library targets, which is the quickest way to diagnoseUnsupportedClassVersionError. - ▸
--release N(Java 9, JEP 247) is safer than-source N -target N: it also compiles against the API of release N, so accidental use of newer methods fails at compile time instead of at run time. - ▸
Multi-release JARs (Java 9) let a library ship
META-INF/versions/21/...classes that override the base classes on newer runtimes, so one artifact can use virtual threads on 21+ and fall back on older JDKs. - ▸
Tools like
jdeps --jdk-internals(find uses of internal APIs) andjdeprscan(find uses of deprecated APIs) are the standard first step in a migration. OpenRewrite recipes can automate many Java 8 to 17/21 source changes.
Remember this
- 1
Release cadence. Java 9 (2017) took three years after Java 8 (2014). From Java 10 (March 2018) a new feature release ships every March and September, numbered 10, 11, 12 and so on. A release includes whatever features are ready; nothing waits for a big-bang version. Each change is described in a numbered JEP (JDK Enhancement Proposal), like JEP 395 for records.
- 2
LTS releases get updates (security and bug fixes) for years from vendors such as Oracle, Eclipse Adoptium, Amazon (Corretto), Microsoft, Azul and Red Hat; non-LTS releases are typically updated only until the next release six months later. The LTS releases are 8, 11, 17, 21 and 25. LTS came every three years at first (11 in 2018, 17 in 2021) and every two years since (21 in 2023, 25 in 2025). Most production systems run an LTS.
- 3
Preview and incubator. A preview language feature is complete but not final: you enable it with
--enable-preview(plus--release Nwhen compiling), and it can change before becoming standard. Records, switch expressions, text blocks and pattern matching all went through previews. An incubator module (likejdk.incubator.vector) is the same idea for APIs. This course tags each feature with the version where it became standard. - 4
Four kinds of change. Each release mixes language features (syntax:
var, records, patterns), library APIs (new classes and methods:List.of,Stream.toList, sequenced collections), JVM and tooling improvements (garbage collectors, JFR,jshell,jwebserver), and removals and restrictions (Java EE modules gone in 11, Nashorn gone in 15, JDK internals locked in 17, Security Manager disabled in 24). Removals are what break upgrades, so they matter as much as new features. - 5
The big themes across 8 to 25: functional programming (lambdas, streams,
Optionalin 8); modularity (9); less boilerplate (var, switch expressions, text blocks, records); data-oriented programming (sealed types, record patterns, pattern switches, Topics 11.1 to 11.3); cheap concurrency (virtual threads in 21, Topic 13.10); low-pause garbage collection (ZGC and Shenandoah); faster start-up (class-data sharing and the AOT cache work); and an easier start for beginners (JShell,java Hello.java, compact source files, Topic 11.5). - 6
How to use this map. To know whether code runs on your JDK, find the newest feature it uses. To plan an upgrade, read the removals between your version and the target. To read old code, recognise the older idioms (anonymous classes,
Collections.unmodifiableList,switchwithbreak) and their modern replacements. You can check your own version withjava -version.
Explain it without notes
What are the LTS releases from Java 8 to 25, and how does the release cadence work?
What is a preview feature, and why should production code avoid it?
Name the most important changes between Java 8 and Java 11 for an application developer.
What changed between Java 11 and Java 17, and what tends to break when upgrading?
What are the headline features of Java 21 and Java 25?
Practice
Modernise this Java 8 idea: sort students by marks (highest first, ties by name) and print name (marks) joined by commas, using a record, var, Comparator combinators and Stream.toList(). Use Ravi 81, Asha 92, Meera 81.
Write one small program that uses a feature from Java 8, 11, 16, 17 and 21, label each in a comment, and state the minimum Java version needed to compile it.
Write a program that prints the running JDK's feature release number, java.version and java.vendor. Which API gives the feature number, and since when?
Trade-offs
- ↔
Staying on an older LTS minimises change, but you miss performance improvements (GC, start-up, compact strings and headers), language features, and eventually free security updates. Upgrading costs testing time and dependency upgrades, mostly at the removal points.
- ↔
Moving to every six-month release gives features sooner but requires upgrading every six months to keep receiving fixes. Most teams track LTS releases and test the in-between releases in CI to catch problems early.
- ↔
Libraries must target the oldest JDK their users run, so they adopt new language features years after applications. Multi-release JARs help, at the cost of build complexity.
Done when you can
Done when you can name the LTS releases and explain the six-month cadence.
Done when you can place a feature (records,
var, text blocks, virtual threads,_) in the release where it became standard.Done when you can list the removals and restrictions that break upgrades from Java 8.
Done when you can explain previews, incubators and
--enable-preview.Done when you can diagnose
UnsupportedClassVersionErrorand use--releasecorrectly.Done when you can outline a safe upgrade plan from Java 8 to 21.