Command Palette

Search for a command to run...

PHASE 0Beginner ~33 min· topic 3 of 8

Topic 0.3

Java Versions, LTS Releases and the Version Tags in This Course

In one line

A new Java version comes out every March and September, and every two years one of them is a Long-Term Support (LTS) release that companies stay on for years: Java 8, 11, 17, 21 and 25. This course tags each feature with the version that introduced it (such as Java 16), and the in-browser Run button uses Java 17.

Think of it like this

School textbooks. Every year the publisher prints a new workbook with a few new exercises, and most schools ignore it. Every few years there is a big new edition that schools buy and keep using for years, getting only small corrections. Java works the same way: a feature release every six months, and every two years an LTS edition that companies adopt and keep for a long time.

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 language features and APIs, such as Java 21. One comes out every six months.
Update release
A small follow-up such as 21.0.9 that fixes bugs and security problems but adds no features.
LTS
Long-Term Support: a release that vendors keep fixing for many years, so companies can stay on it safely. 8, 11, 17, 21 and 25 are LTS.
Preview feature
A finished-but-not-final feature that is switched off unless you pass --enable-preview. It may still change.
Final (standard) feature
A feature that is a permanent part of Java, on by default. Version tags in this course mark when a feature became final.
Backward compatibility
New Java versions still run programs written for older ones.
`--release`
A javac option that compiles for an older Java version and checks you only use language features and library methods that version has.
Class file version
A number inside every .class file saying which Java made it (Java version + 44). A JVM won't load a newer one than it knows.

Step by step

01A short history

Java 1.0 came out in January 1996 from Sun Microsystems. Big releases followed every two or three years: Java 1.2 (1998) added the Collections framework, Java 5 (2004) added generics, enums and the enhanced for loop, and Java 7 (2011) added try-with-resources. Oracle bought Sun in 2010 and has led Java since.

Java 8 (March 2014) was the biggest change to how Java code looks: lambdas and streams. It was so popular that some companies still run it. Then the schedule changed: after Java 9 (September 2017), a release every six months.

A short historydiagram
Rendering diagram…

02How to read a version string

Run java -version and read the first line. 21.0.4 means: feature release 21, interim number 0 (always 0 in practice), update 4. LTS at the end tells you it is a long-term support release. Temurin-21.0.4+7 is the distribution and build number.

The old style is still around: Java 8 prints 1.8.0_231, meaning Java 8, update 231.

terminal
$ java -version
── expected output ──
openjdk version "21.0.4" 2024-07-16 LTS
OpenJDK Runtime Environment Temurin-21.0.4+7 (build 21.0.4+7-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.4+7 (build 21.0.4+7-LTS, mixed mode, sharing)

03What each LTS brought

Java 8 (2014): lambdas and method references, the Streams API, Optional, the java.time date and time library, default methods in interfaces, CompletableFuture.

Java 11 (2018) collects Java 9 to 11: the module system and JShell (9), List.of, Set.of, Map.of (9), var for local variables (10), then in 11 the standard HttpClient, String.isBlank, strip, lines and repeat, Files.readString, and running a single source file with java Main.java. Java 11 also removed the Java EE and CORBA modules from the JDK.

Java 17 (2021) collects 12 to 17: switch expressions and helpful NullPointerException messages (14), text blocks (15), records, pattern matching for instanceof and Stream.toList() (16), sealed classes and strong encapsulation of JDK internals (17).

Java 21 (2023) collects 18 to 21: UTF-8 as the default charset (18), then in 21 virtual threads, sequenced collections (getFirst, getLast, reversed), record patterns, pattern matching for switch and generational ZGC.

Java 25 (2025) collects 22 to 25: unnamed variables and patterns _ and the Foreign Function and Memory API (22), stream gatherers (24), then in 25 compact source files with instance main methods, module import declarations, flexible constructor bodies, scoped values and compact object headers.

04The same idea written in newer Java

Newer versions mostly let you say the same thing with less code. Here is a small data holder written the classic way and as a Java 16 record. Both work on Java 17; the record also gives you equals, hashCode and toString for free (Topic 4.9).

Point.javawhole filejava
// Classic Java: works on every version
final class OldPoint {
    private final int x;
    private final int y;
    OldPoint(int x, int y) { this.x = x; this.y = y; }
    int x() { return x; }
    int y() { return y; }
    // ...plus equals, hashCode and toString written by hand
}

// Java 16 and later: one line
record Point(int x, int y) {}

05Previews: try it before it is final

A preview feature ships complete but locked. You must opt in at compile time and at run time with --enable-preview, and the compiler warns that your code uses a preview. This lets the Java team collect real feedback before making a feature permanent; several previews changed their syntax between releases.

Below, JDK 21 refuses a compact source file (it was a preview in 21, called "unnamed classes" then). It became final in Java 25 as compact source files. Never ship production code that depends on a preview: it may not compile on the next version.

terminal
$ javac Main.java
── expected output ──
Main.java:1: error: unnamed classes are a preview feature and are disabled by default.
void main() {
^
(use --enable-preview to enable unnamed classes)
1 error

06Compiling for an older Java with --release

Your company's servers might run Java 17 while your laptop has JDK 21. javac --release 17 makes the compiler behave like Java 17: newer syntax is rejected, newer library methods are rejected, and the class files are stamped as Java 17 so a Java 17 JVM accepts them.

Here List.getFirst() (added in Java 21) is caught at compile time instead of crashing on the server. Without --release, the code would compile and then fail with NoSuchMethodError when it runs on Java 17.

terminal
$ javac --release 17 Main.java
── expected output ──
Main.java:6: error: cannot find symbol
System.out.println(names.getFirst());
^
symbol: method getFirst()
location: variable names of type List<String>
1 error

07How the tags work in this course

Look for the small version badge next to a topic title or a code block. No badge: works in any Java you're likely to meet. Java 8 to Java 17: runs in the browser with the Run button (it uses Java 17). Java 18 to Java 21: run it with your own JDK 21 or newer. Java 22 and later: shown as code to read, because the browser runner can't run it yet.

Which JDK should you install? The newest LTS your tools support: Java 25 or Java 21. Both run everything in this course. If your job uses Java 17 or 11, the badges tell you exactly which features you can use there.

How the tags work in this coursediagram
Rendering diagram…

Try it yourself

  1. 1

    Find your Java version

    After installing a JDK (Topic 0.4), run java -version and javac -version. Read the feature number, the update number and whether it says LTS. Both commands should show the same feature number; if they don't, two JDKs are installed and your PATH mixes them.

    terminal
    $ javac -version
    ── expected output ──
    javac 21.0.4
  2. 2

    Feel --release catch a newer feature

    Save the tour example as Main.java and compile it with javac --release 11 Main.java. Before you press Enter, predict which lines will fail. The compiler flags the record (Java 16), the switch expression and its arrow rules (Java 14) and the instanceof pattern (Java 16). var, List.of, lambdas and repeat all pass, because they existed by Java 11. Then try --release 16: it compiles.

    terminal
    $ javac --release 11 Main.java
    ── expected output ──
    Main.java:3: error: records are not supported in -source 11
    record Pet(String name, int age) {} // records: Java 16
    ^
    (use -source 16 or higher to enable records)
    Main.java:18: error: switch expressions are not supported in -source 11
    String size = switch (pets.size()) { // switch expressions: Java 14
    ^
    (use -source 14 or higher to enable switch expressions)
    Main.java:19: error: switch rules are not supported in -source 11
    case 0 -> "no pets";
    ^
    (use -source 14 or higher to enable switch rules)
    Main.java:26: error: pattern matching in instanceof is not supported in -source 11
    if (first instanceof Pet p && p.age() < 5) { // pattern matching for instanceof: Java 16
    ^
    (use -source 16 or higher to enable pattern matching in instanceof)
    4 errors
  3. 3

    Spot the versions

    Run the tour example in the browser. Then add System.out.println(pets.getFirst()); and run again. It fails on Java 17 because getFirst is a Java 21 method. Replace it with pets.get(0), which has existed since Java 1.2.

Code & diagrams

A tour through Java versions (runs on Java 17) Java 16+ New tab

Each comment names the version a feature arrived in. The newest feature here is from Java 16, so the browser's Java 17 runs it.

Sign in to run this example in your browser.

Expected output

Total age: 10
Milo is 3
Luna is 7
============
We have many pets
Milo is young
Pet[name=Milo, age=3]
Java 21 features: record patterns and sequenced collections Java 21+ New tab

Needs JDK 21 or newer on your machine. Sealed interfaces (17), record patterns in switch (21) and getFirst/getLast/reversed (21).

This example needs Java 21+. The in-browser compiler is Java 17: install JDK 21 or newer and run it with "java Main.java".

Expected output

a circle of radius 2.0
a square of side 3.0
a big circle
First: Circle[radius=2.0]
Last: Circle[radius=12.0]
Reversed: [Circle[radius=12.0], Square[side=3.0], Circle[radius=2.0]]
Checking the running version in code New tab

Runtime.version() (Java 9) tells a program which Java it is running on. Libraries use this to switch on newer features only when available.

Sign in to run this example in your browser.

Expected output

Running on Java 17 or newer: true
Records available: true
Older than Java 9: false
Java 25: a compact source file Java 25+java

Not runnable in the browser (Java 17). With JDK 25, save as Hello.java and run `java Hello.java`. No class, no static, no String[] args: the compiler wraps it in a class for you. Topic 11.5 covers it.

void main() {
    String name = "Asha";
    IO.println("Hello, " + name + "!");
}

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 newer feature than your target

Compile a file containing record Point(int x, int y) {} with javac --release 11 Main.java.

terminal
$ javac --release 11 Main.java
── what you'll see ──
Main.java:1: error: records are not supported in -source 11
record Point(int x, int y) {}
^
(use -source 16 or higher to enable records)
1 error

Break #2

Use a preview feature without opting in

With JDK 21, write void main() { System.out.println("Hello"); } with no class around it, and compile.

terminal
$ javac Main.java
── what you'll see ──
Main.java:1: error: unnamed classes are a preview feature and are disabled by default.
void main() {
^
(use --enable-preview to enable unnamed classes)
1 error

Myth vs fact

Myth

Java 1.8 and Java 8 are different versions.

Fact

They are the same. Before Java 9, version strings started with 1., so Java 8 calls itself 1.8.0_xxx.

Myth

Only LTS versions are stable, and the others are betas.

Fact

Every feature release is production quality. Non-LTS releases simply stop receiving updates after six months, so you must keep upgrading. Unfinished features are marked as previews and are off by default.

Myth

Upgrading Java means rewriting my code.

Fact

Java's compatibility is very strong. Most upgrade work is updating libraries and build tools that read bytecode or used JDK internals, not changing your own code.

Myth

Java is old, so it hasn't changed much.

Fact

Since 2014 Java gained lambdas, streams, modules, var, records, sealed classes, pattern matching, text blocks and virtual threads. Code written in Java 25 style looks quite different from Java 6 code.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Class file major versions: Java 8 = 52, 11 = 55, 17 = 61, 21 = 65, 25 = 69 (feature + 44). An old JVM fails with UnsupportedClassVersionError. Bytecode tools (ASM, Byte Buddy, Mockito, Jacoco, Spring's class scanning) must also understand each new version, which is the usual reason an upgrade needs library bumps.

  • ▸

    --release N (Java 9, JEP 247) is stricter than the old -source N -target N pair: it also compiles against the N version of the public API signatures, so calling List.getFirst() with --release 17 is a compile error rather than a NoSuchMethodError in production. Each JDK supports only a window of old releases: JDK 21's javac rejects --release 7 with error: release version 7 not supported.

  • ▸

    Feature lifecycle is visible in JEPs (JDK Enhancement Proposals): incubator modules (jdk.incubator.*, such as the Vector API), previews (--enable-preview), then final. Removals follow a deprecation path: @Deprecated(forRemoval = true), then removal. The Security Manager was deprecated for removal in 17 and permanently disabled in 24; finalize() is deprecated for removal.

  • ▸

    Upgrades bring free performance. JDK 21 made generational ZGC available, JDK 24 removed the pinning problem of virtual threads in synchronized blocks, and JDK 25 made compact object headers a product option (-XX:+UseCompactObjectHeaders), shrinking every object's header. Running the same jar on a newer LTS often lowers memory and latency with no code change.

Remember this

  1. 1

    Java has numbers, not names. Up to Java 8 the official version string started with 1. (Java 8 reports 1.8.0_231), which is why you still see "Java 1.8" in old documents; it means Java 8. From Java 9 onwards the number is just the number: 11.0.24, 17.0.12, 21.0.9. The first number is the feature release, and the later numbers count updates, which bring security and bug fixes but no new language features.

  2. 2

    Since Java 10 (March 2018) a new feature release ships every six months, in March and September. Each release is smaller than the big multi-year releases of the past, so upgrading is less scary. A non-LTS release gets updates only until the next one comes out, six months later.

  3. 3

    Some releases are marked LTS (Long-Term Support): Java 8 (2014), 11 (2018), 17 (2021), 21 (2023) and 25 (2025). Vendors such as Eclipse Adoptium, Oracle, Amazon and Microsoft keep publishing security updates for LTS versions for many years. Most companies run an LTS version in production. Since Java 17, a new LTS arrives every two years, each September.

  4. 4

    Each LTS brought something big. Java 8: lambdas, streams, Optional and the java.time dates library. Java 11: var (from 10), List.of and JShell (from 9), new String methods, the built-in HTTP client and single-file java Main.java. Java 17: switch expressions, text blocks, records, pattern matching for instanceof and sealed classes. Java 21: virtual threads, sequenced collections, record patterns and pattern matching for switch. Java 25: compact source files and instance main methods, module imports, flexible constructor bodies and scoped values.

  5. 5

    New features often appear first as a preview: complete and usable, but switched off unless you pass --enable-preview, and allowed to change based on feedback. A feature counts as arriving in the release where it became final (standard). In this course, the version tag always means the final version.

  6. 6

    The version tags in this course. A badge such as Java 16 on a topic or code block means "you need at least Java 16 for this". Topics with no badge use Java basics that work everywhere. The Run button in your browser runs Java 17, so every untagged example and every example tagged Java 17 or lower runs there. Examples tagged Java 18 to 21 are checked with a real JDK 21 and you run them on your own machine (Topic 0.4). Features from Java 22 onwards are shown as code you read, with their version badge.

  7. 7

    Java takes backward compatibility seriously: code written for Java 8 almost always compiles and runs on Java 25. The exceptions are code that used internal JDK classes (sun.misc.*), removed modules (Java EE classes left the JDK in 11) or tools that read bytecode and must understand each new class file version. That is why upgrades sometimes need library updates even when your own code is fine.

Explain it without notes

01

What does LTS mean, and why do most companies run an LTS version?

02

List the five LTS versions and one headline feature for each.

03

What is a preview feature, and how do you use one?

04

What does javac --release 17 do, and why is it better than just compiling with the newest JDK?

05

What does a Java 21 badge on a code example in this course mean for you?

Practice

01

Rewrite this Java 14 switch expression as an old-style if/else chain that works on Java 8: String size = switch (n) { case 0 -> "none"; case 1 -> "one"; default -> "many"; }; with n = 1, and print the result.

02

Write a program that uses "ab".repeat(3) (Java 11) and " hi ".strip() (Java 11), and prints both results inside square brackets.

03

Write a program that prints whether the running Java has virtual threads (Java 21) using Runtime.version().feature(). What does it print in the browser?

Trade-offs

  • ↔

    Staying on an LTS gives stability and long support, but you wait up to two years for new features and performance work. Following every six-month release gives you everything early, but you must upgrade twice a year.

  • ↔

    Targeting an older release (--release 11) lets your library run for more users, but you can't use records, switch expressions or newer APIs. Libraries often target the oldest supported LTS; applications can target what they deploy on.

  • ↔

    Previews let you try the future, but code using them is tied to one JDK version and may break on the next.

Done when you can

  • Done when you can read a Java version string like 21.0.4 and the old 1.8.0_231.

  • Done when you can name the five LTS versions and one big feature from each.

  • Done when you can explain the six-month cadence and what LTS means.

  • Done when you can explain what a preview feature is and how to enable one.

  • Done when you know what the version badges mean and that the browser Run button uses Java 17.

  • Done when you can use javac --release to target an older Java.