Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Java  Module 1 – Start here: JDK 25 LTS and your first programs

Java versions, LTS and preview features

Read a JEP, run a preview feature with --enable-preview, see why previews change between releases, and tell final, preview and incubator features apart.

  • Beginner
  • 20 minutes
  • Examples run with Java 25.0.4.1 and Python 3.14.8
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Read a JEP page and find its status, release and type
  • Run a preview feature with --enable-preview and explain why previews may change
  • Use this track's version labels to tell final, preview and incubator features apart
  • Compile for an older release with javac --release, and explain why many teams build on an LTS release

Before you start

What you’ll learn · Before you start

What you will learn

  • Read a JEP page and find its status, release and type
  • Run a preview feature with --enable-preview and explain why previews may change
  • Use this track's version labels to tell final, preview and incubator features apart
  • Compile for an older release with javac --release, and explain why many teams build on an LTS release

Before you start

On this page

Every Java feature reaches you in one of three states. A final feature is a permanent part of the Java SE platform and works without any option. A preview feature: A fully specified and implemented feature that is not yet permanent. It needs --enable-preview to compile and to run, its class files carry minor version 65535, and a later release may change or remove it. is fully specified and implemented but not yet permanent: it is switched off unless you compile and run with --enable-preview, and it may still change or disappear in the next release. An incubator module: A module whose name starts with jdk.incubator and that holds an API not yet ready for preview. It needs --add-modules, the JVM warns when it is used, and its packages are renamed when it is promoted. feature is an API in a module whose name starts with jdk.incubator, which the JDK leaves out until you add it with --add-modules, and which is expected to change. Each change to the JDK is described in a JDK Enhancement Proposal, a JEP: A JDK Enhancement Proposal: the document that describes one change to the JDK, with its type, scope, status and the release that delivers it, at openjdk.org/jeps/ followed by its number., whose title and header tell you which state a feature is in and which release carries it. The states matter when you choose a release: preview code is tied to one release, and tools take time to support each new one, which is why many teams build on an LTS release such as JDK 21 or JDK 25.

A JEP’s title and header tell you a feature’s state

Every JEP has a page at openjdk.org/jeps/ followed by its number. Its header holds the facts you need, and its title carries a label while the feature is not final:

  • The title suffix, such as (Preview), (Third Preview) or (Tenth Incubator), says that the feature is not final yet and which round of previewing or incubating this is. A final feature’s JEP has a plain title.
  • Type (Feature, Infrastructure, Informational or Process) says whether the JEP changes the JDK or describes how OpenJDK works.
  • Scope is SE for a change to the Java SE specification, which every compatible JDK has; JDK for a feature of the JDK outside the specification, such as the incubating Vector API; and Implementation for a change inside the JDK’s code alone, such as which garbage collector is the default.
  • Status (Candidate, Targeted, Integrated, Closed / Delivered) says how far the JEP has come; Closed / Delivered means it shipped in the release that the Release field names.
  • Release is the JDK release that delivered, or will deliver, the JEP, such as 25.

The status follows a fixed path. A JEP is drafted and submitted; as a Candidate it is on the roadmap but promised to no release; its owner then proposes it for one release, and once the plan is accepted for that release it is Targeted; its code then goes into the main code base (Integrated), its tests follow (Complete), and when that release ships it is Closed as Delivered. A JEP can also end as Withdrawn or Rejected. Step through the path, including the way back for a JEP that is dropped from its release:

Interactive · stage by stage The states a feature JEP passes through Step through the life of a feature JEP, from its draft to the release that ships it, and the ways it can leave the path.
The states a feature JEP passes through, step 9 of 9. Showing Draft, Submitted, Candidate, Proposed to Target, Targeted, Integrated, Complete, Closed / Delivered. In focus: Closed / Delivered.droppedDraftwritten and revisedSubmittedready to be evaluatedCandidateon the roadmap, no releaseProposed to Targeta plan for one releaseTargetedaccepted for that releaseIntegratedin the main code baseCompleteonly bug fixes expectedClosed / Deliveredthe release has shipped

The drawing shows the whole scene; its 9 steps are listed below.

Every step as text
  1. Draft. A JEP starts as a Draft, which its owner writes and revises until it is endorsed.
  2. Submitted. The owner submits it, declaring it ready to be evaluated for the JDK Roadmap.
  3. Candidate. The OpenJDK Lead adds it to the roadmap as a Candidate, which is when it receives its number. A Candidate is an idea worth considering, promised to no release.
  4. Proposed to Target. Once a credible engineering plan is written down, the owner proposes the JEP for one specific release.
  5. Targeted. The Project Lead of that release accepts it once the project's developers broadly agree, and the JEP is Targeted.
  6. Dropped. A Targeted JEP can still be dropped from its release. If the drop is accepted, it goes back to Candidate and waits for another release.
  7. Integrated. Its code and unit tests go into the main code base, and the JEP is Integrated.
  8. Complete. When its functional, performance and conformance tests are delivered, the JEP is Complete, and only bug fixes are expected.
  9. Closed / Delivered. When the release ships, the JEP is Closed / Delivered, and its Release field names that release. A JEP can also end as Closed / Withdrawn, by its owner, or Closed / Rejected.

The project page of each release, such as openjdk.org/projects/jdk/27/, lists the JEPs it delivers, which is the quickest way to see what a release contains.

Checkpoint

Check: Reading a JEP

  1. Question 1 of 2 A JEP is titled "Vector API (Tenth Incubator)". What does the suffix in parentheses tell you?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The feature is not final, and this is its tenth round of incubating

    A final feature's JEP has a plain title. While a feature is not final, its title carries a suffix that names the state, preview or incubator, and the round, such as Tenth Incubator for the Vector API in JDK 25.

  2. Question 2 of 2 A JEP's header says Scope SE. What does that tell you about the feature?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It is part of the Java SE specification, so every compatible JDK has it

    Scope SE marks a change to the Java SE platform itself, which every compatible JDK implements. Scope JDK marks a feature of the JDK outside the specification, such as the Vector API, and Scope Implementation a change inside the code alone, such as the default garbage collector of JEP 523.

A preview is off by default and tied to one release

JDK 25 previews primitive types in patterns (JEP 507, its third preview), which let a switch on an int use patterns such as case int c when c >= 500. This program uses them:

Status.java Java · Status.java
// Primitive type patterns in switch: a preview language feature in JDK 25 (JEP 507) and JDK 27 (JEP 532).
public class Status {
    static String describe(int code) {
        return switch (code) {
            case 200 -> "OK";
            case int c when c >= 500 -> "server error " + c;
            case int c -> "other status " + c;
        };
    }

    public static void main(String[] args) {
        System.out.println(describe(200));
        System.out.println(describe(503));
        System.out.println(describe(404));
    }
}

The session compiles and runs it with and without previews on JDK 25, then tries the class file on JDK 27:

A preview feature on JDK 25, then on JDK 27 Python · preview_session.py
from session import run

# A preview feature on JDK 25: rejected by default, compiled and run with --enable-preview, and tied to its release.
run("""
javac -d out Status.java
javac --release 25 --enable-preview -d out Status.java
javap -v out/Status.class | grep -E "(minor|major) version"
java -cp out Status
java --enable-preview -cp out Status
javac --release 24 --enable-preview -d out24 Status.java
""", expect_failure=[
    "javac -d out Status.java",
    "java -cp out Status",
    "javac --release 24 --enable-preview -d out24 Status.java",
])
# The same class file on the next releases' JVM, previews switched on.
run("java --enable-preview -cp out Status", jdk="27", expect_failure=["java --enable-preview -cp out Status"])

Output

# JDK in use: openjdk version "25.0.4.1" LTS
$ javac -d out Status.java
Status.java:6: error: primitive patterns are a preview feature and are disabled by default.
            case int c when c >= 500 -> "server error " + c;
                 ^
  (use --enable-preview to enable primitive patterns)
1 error
[exit status 1]
$ javac --release 25 --enable-preview -d out Status.java
Note: Status.java uses preview features of Java SE 25.
Note: Recompile with -Xlint:preview for details.
$ javap -v out/Status.class | grep -E "(minor|major) version"
  minor version: 65535
  major version: 69
$ java -cp out Status
Error: LinkageError occurred while loading main class Status
	java.lang.UnsupportedClassVersionError: Preview features are not enabled for Status (class file version 69.65535). Try running with '--enable-preview'
[exit status 1]
$ java --enable-preview -cp out Status
OK
server error 503
other status 404
$ javac --release 24 --enable-preview -d out24 Status.java
error: invalid source release 24 with --enable-preview
  (preview language features are only supported for release 25)
[exit status 2]
# JDK in use: openjdk version "27"
$ java --enable-preview -cp out Status
Error: LinkageError occurred while loading main class Status
	java.lang.UnsupportedClassVersionError: Status (class file version 69.65535) was compiled with preview features that are unsupported. This version of the Java Runtime only recognizes preview features for class file version 71.65535
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 preview_session.py

Each step shows one of the rules of JEP 12:

  • Off by default. Plain javac refuses the program: primitive patterns are a preview feature and are disabled by default.
  • Opt in when compiling. javac --release 25 --enable-preview compiles it and adds two notes saying so. The --enable-preview option is accepted only together with the current release, so the attempt with --release 24 is refused.
  • The class file is marked. javap reports minor version 65535 under the usual major version 69: all 16 bits of the minor version are set, which the JVM specification reserves for class files that depend on the preview features of that release.
  • Opt in again when running. java without the option will not load the class and suggests --enable-preview; with it, the program runs.
  • Previews never cross releases. JDK 27’s JVM refuses the class even with previews enabled, because it only recognises the preview features of its own release, class file version 71.65535. The source has to be compiled again for JDK 27, and it compiles only if the preview has not changed in between.

Step through the same session from the point of view of the class file:

Interactive · step by step One preview class file, two JVMs Step through the session above, from the first refused compile to the JVM of JDK 27.
One preview class file, two JVMs, step 7 of 7. Showing Status.java, Status.class (version 69.65535), JDK 25 JVM (ran: OK, server error 503), JDK 27 JVM (refused: wants 71.65535). In focus: Status.java, JDK 27 JVM.javacStatus.javaa primitive patternStatus.classversion 69.65535JDK 25 JVMran: OK, server error 503JDK 27 JVMrefused: wants 71.65535

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Off by default. Plain javac refuses Status.java, because primitive patterns are a preview feature and are disabled by default.
  2. Opt in to compile. javac --release 25 --enable-preview compiles it, with two notes saying so. The option is accepted only together with the current release, so --release 24 is refused.
  3. The class file is marked. javap reports minor version 65535 under the usual major version 69. All 16 bits of the minor version are set, which marks a class file that depends on the preview features of JDK 25.
  4. Run without the option. Without --enable-preview, the JDK 25 JVM will not load the class, and its error suggests running with --enable-preview.
  5. Opt in to run. With --enable-preview the class runs and prints OK, server error 503 and other status 404.
  6. The next release. The JVM of JDK 27 refuses the class even with previews enabled, because it recognises only the preview features of its own release, class file version 71.65535.
  7. Compile again. So the source has to be compiled again for JDK 27, and it compiles only if the preview has not changed in between.

That last rule is the reason for a warning in JEP 12: class files that rely on preview features are for the developer who opted in, and are not meant to be shipped to anyone else’s machines.

Hex Viewer & File Inspector Open out/Status.class: the bytes after CA FE BA BE are FF FF, the minor version 65535 of a preview class file.

Checkpoint

Check: The rules for preview features

  1. Question 1 of 2 Which minor version does javap report for a class file compiled with --enable-preview on JDK 25?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 65535

    All 16 bits of the minor version are set, giving 65535, under the usual major version 69. The JVM loads such a class file only when previews are enabled, and only on the release whose previews it uses.

  2. Question 2 of 2 You run the JDK 25 preview class file Status.class on JDK 27 with --enable-preview. What happens?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: JDK 27 refuses to load it, because it only accepts the preview features of its own release

    The JVM of each release loads preview class files only of its own version, 71.65535 for JDK 27. Compile the source again for JDK 27; it compiles only if the preview has not changed.

Previews really do change between releases

A feature usually needs several JEPs, one per round. The diagram follows two of them:

Two preview histories: compact source files, previewed in JDK 21 to 24 and final in 25; stable values, renamed lazy constants in JDK 26.Compact source filesLazy constantsJDK 21: preview (JEP 445)called unnamed classesJDK 22 and 23: second andthird previews (JEPs 463, 477)JDK 24: fourth preview (JEP 495)called simple source filesJDK 25: final (JEP 512)compact source filesJDK 25: preview (JEP 502)called stable valuesJDK 26: second preview (JEP 526)renamed lazy constantsJDK 27: third preview (JEP 531)Final, changed again or removed:a later release decides

Two features on their way through previews

Text description of the diagram

The diagram shows two features moving through preview releases, each from top to bottom.

Compact source files: a first preview in JDK 21 (JEP 445), where they were called unnamed classes; a second preview in JDK 22 (JEP 463); a third in JDK 23 (JEP 477); a fourth in JDK 24 (JEP 495), where they were called simple source files; and final in JDK 25 (JEP 512) as compact source files.

Lazy constants: a first preview in JDK 25 (JEP 502), under the name stable values; a second preview in JDK 26 (JEP 526), where the API was renamed lazy constants; and a third preview in JDK 27 (JEP 531). Whether the feature becomes final, changes again or is removed is decided in a later release.

Compact source files, the subject of lesson 3, went through four previews under three different names before JEP 512 made them final in JDK 25. Stable values, previewed in JDK 25, were renamed lazy constants in their second preview in JDK 26 and are in their third preview in JDK 27. JEP 12, the rules for previews, says that two rounds of previewing are the norm, and that a preview ends either as a permanent feature, with or without changes, or by being removed.

A preview API can be renamed or reshaped from one release to the next. This file uses the stable values preview of JDK 25:

Settings.java (JDK 25 preview) Java · Settings.java
// A value computed once, on first use, with the StableValue preview API of JDK 25 (JEP 502).
final StableValue<String> greeting = StableValue.of();

void main() {
    IO.println(greeting.orElseSet(() -> "computed once"));
    IO.println(greeting.orElseSet(() -> "never used"));
}
Lazy.java (JDK 26 and 27 preview) Java · Lazy.java
// The same idea with the LazyConstant preview API that replaced StableValue (JEP 526 in JDK 26, JEP 531 in JDK 27).
final LazyConstant<String> greeting = LazyConstant.of(() -> "computed once");

void main() {
    IO.println(greeting.get());
    IO.println(greeting.get());
}
One preview API, two releases Python · churn_session.py
from session import run

# A preview API on JDK 25, the same file on JDK 27, and the renamed API on JDK 27.
run("java --enable-preview Settings.java")
run("""
java --enable-preview Settings.java
java --enable-preview Lazy.java
""", jdk="27", expect_failure=["java --enable-preview Settings.java"])

Output

# JDK in use: openjdk version "25.0.4.1" LTS
$ java --enable-preview Settings.java
computed once
computed once
# JDK in use: openjdk version "27"
$ java --enable-preview Settings.java
Settings.java:2: error: cannot find symbol
final StableValue<String> greeting = StableValue.of();
      ^
  symbol:   class StableValue
  location: class Settings
Settings.java:2: error: cannot find symbol
final StableValue<String> greeting = StableValue.of();
                                     ^
  symbol:   variable StableValue
  location: class Settings
2 errors
error: compilation failed
[exit status 1]
$ java --enable-preview Lazy.java
computed once
computed once

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 churn_session.py

On JDK 25, with previews enabled, Settings.java computes its value once and prints it twice. On JDK 27 the same file no longer compiles: cannot find symbol for StableValue, because JEP 526 renamed the API to LazyConstant and removed low-level methods such as orElseSet. Lazy.java, written for the new API, runs on JDK 27. Code that relied on the preview had to be rewritten, which is exactly the risk a preview carries.

Key idea

A preview can be renamed, reshaped or removed in the next release, so code that uses one may have to be rewritten, as StableValue became LazyConstant between JDK 25 and JDK 26.

Checkpoint

Check: Previews that change

  1. Question 1 of 2 Settings.java ran on JDK 25 with --enable-preview, but on JDK 27 it failed with cannot find symbol for StableValue. Why?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The preview API was renamed to LazyConstant in JDK 26, and StableValue no longer exists

    A preview API can be renamed or reshaped between releases. Stable values were renamed lazy constants in their second preview, so code written for the JDK 25 preview had to be rewritten.

  2. Question 2 of 2 According to JEP 12, how does a preview end?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It becomes a permanent feature, with or without changes, or it is removed

    JEP 12 says two rounds of previewing are the norm, and that a preview ends either as a permanent feature, with or without changes, or by being removed.

An incubator module stays out until you add it

An incubator module holds an API that is not ready for the preview process yet. The Vector API, which lets a program work on several numbers in one processor instruction, has incubated in the module jdk.incubator.vector since JDK 16; JDK 25 carries its tenth round and JDK 27 its twelfth. Its JEP says it will stay there until features of Project Valhalla that it needs, such as value objects, become available as previews; value objects are targeted to JDK 28 as a preview.

Lanes.java Java · Lanes.java
// The Vector API is an incubating API in the module jdk.incubator.vector.
import jdk.incubator.vector.IntVector;
import jdk.incubator.vector.VectorSpecies;

void main() {
    VectorSpecies<Integer> species = IntVector.SPECIES_128;
    IO.println("int lanes in a 128-bit vector: " + species.length());
    IntVector a = IntVector.fromArray(species, new int[] {1, 2, 3, 4}, 0);
    IntVector b = IntVector.fromArray(species, new int[] {10, 20, 30, 40}, 0);
    IO.println("lane-wise sum: " + a.add(b));
}
An incubator module on JDK 25 Python · incubator_session.py
from session import run

# An incubator module is not resolved unless you ask for it with --add-modules.
run("""
java Lanes.java
java --add-modules jdk.incubator.vector Lanes.java
""", expect_failure=["java Lanes.java"])

Output

# JDK in use: openjdk version "25.0.4.1" LTS
$ java Lanes.java
Lanes.java:2: error: package jdk.incubator.vector is not visible
import jdk.incubator.vector.IntVector;
                    ^
  (package jdk.incubator.vector is declared in module jdk.incubator.vector, which is not in the module graph)
Lanes.java:3: error: package jdk.incubator.vector is not visible
import jdk.incubator.vector.VectorSpecies;
                    ^
  (package jdk.incubator.vector is declared in module jdk.incubator.vector, which is not in the module graph)
2 errors
error: compilation failed
[exit status 1]
$ java --add-modules jdk.incubator.vector Lanes.java
WARNING: Using incubator modules: jdk.incubator.vector
int lanes in a 128-bit vector: 4
lane-wise sum: [11, 22, 33, 44]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 incubator_session.py

Without --add-modules jdk.incubator.vector the compiler cannot see the package at all, because incubator modules are left out of the default set of modules. With the option the program runs, and the JVM prints WARNING: Using incubator modules first: JEP 11 requires a warning whenever an incubator module is used. When an incubating API is finally promoted, its packages are renamed, so even the import lines of code like this will change.

Checkpoint

Check: Incubator modules

  1. Question 1 of 2 What makes the package jdk.incubator.vector visible to the compiler?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: --add-modules jdk.incubator.vector

    Without --add-modules jdk.incubator.vector, the module is not in the module graph, so its packages are not visible. With it, Lanes.java ran, after the incubator warning.

  2. Question 2 of 2 When the Vector API is finally promoted out of incubation, what happens to the import lines of Lanes.java?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: They change, because an incubating API's packages are renamed when it is promoted

    An incubating API is expected to change, and when it is promoted to a preview or a final module its packages are renamed, so even the import lines of code like Lanes.java will change.

Final, preview and incubator features ask different things of your code

The three states side by side:

  • Final features are always on and change only through deprecation and removal over several releases; their class files are ordinary. Compact source files (JEP 512) are an example in JDK 25. Use them for all code you ship.
  • Preview features are switched on with --enable-preview when compiling and when running, and may change or be removed until a later JEP makes them final; their class files carry minor version 65535 and load only on the same feature release. Primitive types in patterns (JEP 507) and stable values (JEP 502) are examples in JDK 25. Use them for experiments and for feedback on the release that has them.
  • Incubator features are switched on with --add-modules for their module, and their packages are renamed when they are promoted; their class files are ordinary, but the JVM warns when the module is used. The Vector API (JEP 508) is the example in JDK 25. Use it for experiments, with code you expect to rewrite.

Whether your code may use a state depends on where the code goes. Answer two questions about it in the table:

Interactive · answer and compare Can your code use a final, a preview or an incubator feature? Answer two questions about the code you are writing. The table rules out the states that do not fit it, and says why.
Will the code be shipped to other people's machines?
Must the code keep compiling and running, unchanged, on the next LTS release?

Answer the questions above: the table then marks what fits your case and says why the rest does not.

The three states of a Java feature, against how you switch them on, their class files and how much they may change
State Switched on with Class files May still change
Final Example in JDK 25: compact source files (JEP 512) Good nothing: always on Good ordinary, and they run on later releases Good only through deprecation and removal over several releases
Preview Example in JDK 25: primitive types in patterns (JEP 507) Mixed --enable-preview, when compiling and when running Poor minor version 65535; they load only on the same feature release Poor yes, or be removed, until a later JEP makes it final
Incubator Example in JDK 25: the Vector API (JEP 508) Mixed --add-modules for its module Mixed ordinary, but the JVM warns when the module is used Poor yes, and its packages are renamed when it is promoted
How the answers decide
  • “Will the code be shipped to other people's machines?” Yes, it ships: rules out Preview. JEP 12 says that class files which rely on preview features are for the developer who opted in, not for other people's machines.
  • “Will the code be shipped to other people's machines?” Yes, it ships: adds a caveat to Incubator. An incubating API is expected to change, and its packages are renamed when it is promoted, so shipped code will need rewriting.
  • “Must the code keep compiling and running, unchanged, on the next LTS release?” Yes: rules out Preview. Preview class files load only on their own feature release, and the source compiles again only if the preview has not changed.
  • “Must the code keep compiling and running, unchanged, on the next LTS release?” Yes: adds a caveat to Incubator. The Vector API has incubated since JDK 16 and is expected to change, so the next LTS may need new code or new imports.
  • “Will the code be shipped to other people's machines?” No, it is an experiment: For an experiment every state works, as long as you pass --enable-preview or --add-modules on the release that has the feature.

In short: shipped code and code that must survive the next LTS use final features only, while an experiment may use any of the three on the release that has it. This track marks each state in its lessons:

How this track labels versions

A lesson without a label uses only what is final in JDK 25 LTS. JDK 26+ or JDK 27+ marks something that is final only from that release on and does not compile on JDK 25. Preview in 25 or Preview in 27 marks a preview feature: the example needs --enable-preview on exactly that release, and the feature may change. Incubator marks an incubating API that needs --add-modules. Targeted to JDK 28 (not released) marks a JEP for a release that has not shipped, which you can only read about or try in early-access builds.

Checkpoint

Check: The three states and their labels

  1. Question 1 of 2 A JEP has a plain title, Status Closed / Delivered and Release 26. Which label does this track give an example that uses its feature?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: JDK 26+

    A plain title and Closed / Delivered mean a final feature, and the Release field says it arrived in JDK 26. The track marks such features JDK 26+, because they do not compile on JDK 25.

  2. Question 2 of 2 An example in a later lesson is labelled Preview in 27. What do you need to run it?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: JDK 27 itself, with --enable-preview when compiling and when running

    Preview in 27 means the example needs --enable-preview on exactly JDK 27, and the feature may still change. Its class files would load only on JDK 27 with previews enabled.

Many teams build on an LTS release, and javac compiles for older ones

Three facts from this lesson and lesson 1 explain the habit:

  1. Updates last longer. A feature release receives updates for six months; an LTS release for years. Adoptium lists Temurin 25 until at least 2031.
  2. Tools need time. JaCoCo 0.8.15, a code-coverage tool for Java, supports Java 26 officially but reads Java 27 class files only experimentally, and Spring Boot 4.1.1 is documented as compatible with Java up to 26. Right after a feature release, some of your tools may not support it yet.
  3. Previews stay behind. Code that depends on a preview is tied to one feature release and has to be recompiled, and often rewritten, for the next one.

When code must run on an older release than your JDK, compile it with javac’s --release option: The javac option that compiles for an older Java release: by that release's language rules, against its API and into class files of its version, so javac --release 21 writes version 65., --release. The javac documentation says the option compiles according to the rules of the language for that release, against that release’s API, and writes class files that target it:

Recent.java Java · Recent.java
// Uses List.reversed(), a method that arrived in JDK 21 with sequenced collections.
import java.util.List;

public class Recent {
    public static void main(String[] args) {
        List<String> releases = List.of("21", "25", "27");
        System.out.println("Newest first: " + releases.reversed());
    }
}
javac --release 21 and --release 17 on JDK 25 Python · release_flag_session.py
from session import run

# javac --release N compiles against the language and the API of release N, and writes class files for it.
run("""
javac --release 21 -d out21 Recent.java
javap -v out21/Recent.class | grep "major version"
javac --release 17 -d out17 Recent.java
""", expect_failure=["javac --release 17 -d out17 Recent.java"])
run("java -cp out21 Recent", jdk="21")

Output

# JDK in use: openjdk version "25.0.4.1" LTS
$ javac --release 21 -d out21 Recent.java
$ javap -v out21/Recent.class | grep "major version"
  major version: 65
$ javac --release 17 -d out17 Recent.java
Recent.java:7: error: cannot find symbol
        System.out.println("Newest first: " + releases.reversed());
                                                      ^
  symbol:   method reversed()
  location: variable releases of type List<String>
1 error
[exit status 1]
# JDK in use: openjdk version "21.0.12.1" LTS
$ java -cp out21 Recent
Newest first: [27, 25, 21]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 release_flag_session.py

With --release 21, JDK 25’s compiler wrote a class file of version 65, which JDK 21 ran. With --release 17 it refused the program, because List.reversed() does not exist in the Java 17 API: the release option checks the library you call as well as the class file version.

Key idea

An LTS release keeps receiving updates for years, and javac --release N builds class files for release N, checked against that release’s language rules and API.

Checkpoint

Check: LTS releases and javac --release

  1. Question 1 of 2 javac --release 17 on JDK 25 refused Recent.java with cannot find symbol for reversed(). Why?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: List.reversed() is not in the Java 17 API, and --release checks the library you call

    The release option compiles by the language rules of that release, against that release's API, and writes class files for it. Java 17's List has no reversed(), so the compiler refuses the call.

  2. Question 2 of 2 Which of these does the lesson give as reasons why many teams build on an LTS release?

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • Updates last for years instead of six months
    • Code that depends on a preview stays tied to one release
    • Tools need time to support each new release

    Three facts explain the habit: longer updates, tools that take time to catch up, and previews that stay behind with their release.

Practise: classify ten features

For each feature below, open its JEP page (openjdk.org/jeps/ and the number) and decide its state in the JDK 25 that this track uses: final in JDK 25, final only in a later release, a preview, or an incubator. The title suffix and the Release field are all you need, and each answer explains what the page says.

Checkpoint

Features 1 to 3

  1. Question 1 of 3 JEP 512, Compact Source Files and Instance Main Methods: what is its state in JDK 25?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: Final in JDK 25

    JDK 25 delivered it with a plain title: the feature is final, after four previews in JDK 21 to 24.

  2. Question 2 of 3 JEP 511, Module Import Declarations: what is its state in JDK 25?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: Final in JDK 25

    Its Release field is 25 and its title has no suffix, so JDK 25 made it final; compact source files rely on it to import java.base.

  3. Question 3 of 3 JEP 513, Flexible Constructor Bodies: what is its state in JDK 25?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: Final in JDK 25

    Release 25, no suffix: final in JDK 25. It lets a constructor run statements before calling super(...) or this(...).

Checkpoint

Features 4 to 6

  1. Question 1 of 3 JEP 506, Scoped Values: what is its state in JDK 25?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: Final in JDK 25

    Release 25 and a plain title: scoped values became final in JDK 25 after several previews.

  2. Question 2 of 3 JEP 505, Structured Concurrency (Fifth Preview): what is its state in JDK 25?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: A preview feature in JDK 25

    The suffix says it: a fifth preview in JDK 25. JDK 26 and 27 previewed it again, and JEP 543 proposes to make it final.

  3. Question 3 of 3 JEP 507, Primitive Types in Patterns, instanceof, and switch (Third Preview): what is its state in JDK 25?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: A preview feature in JDK 25

    A third preview in JDK 25, the feature that Status.java uses with --enable-preview. JDK 27 carries its fifth preview (JEP 532).

Checkpoint

Features 7 and 8

  1. Question 1 of 2 JEP 502, Stable Values (Preview): what is its state in JDK 25?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A preview feature in JDK 25

    A first preview in JDK 25. Its second preview in JDK 26 renamed it lazy constants, so the JDK 25 code no longer compiles on JDK 27.

  2. Question 2 of 2 JEP 508, Vector API (Tenth Incubator): what is its state in JDK 25?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: An incubator feature in JDK 25

    An incubator API in the module jdk.incubator.vector, used with --add-modules, as in Lanes.java.

Checkpoint

Features 9 and 10

  1. Question 1 of 2 JEP 517, HTTP/3 for the HTTP Client API: what is its state in JDK 25?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Final, but only in a release after JDK 25

    Its Release field is 26: the feature is final, but only from JDK 26 on, so this track would label it JDK 26+.

  2. Question 2 of 2 JEP 523, Make G1 the Default Garbage Collector in All Environments: what is its state in JDK 25?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Final, but only in a release after JDK 25

    Delivered in JDK 27, so it is not part of JDK 25; this track would label it JDK 27+.

With the ten features classified, practise the six ideas of the lesson in a short session. It begins where your answers so far were weakest, and every answer is explained before the next question:

Practice that adapts to you

Practise this lesson

13 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 13 On a JEP page, which field names the JDK release that delivered, or will deliver, the JEP?

    Choose one answer.

    Show the answer: Question 1 of 13

    Answer: Release

    The header's Release field names the release. Together with the title suffix it tells you a feature's state in a given JDK.

  2. Question 2 of 13 A JEP that interests you has the status Candidate. Will its feature be in JDK 28?

    Choose one answer.

    Show the answer: Question 2 of 13

    Answer: Not necessarily, because a Candidate is on the roadmap but promised to no release

    The path runs Draft, Submitted, Candidate, Proposed to Target, Targeted, Integrated, Complete and Closed / Delivered. Only from Targeted on is a JEP accepted for a particular release, and even then it can be dropped.

  3. Question 3 of 13 A JEP with a plain title shows Status Closed / Delivered and Release 26. Can a program use its feature on JDK 25 without any option?

    Choose one answer.

    Show the answer: Question 3 of 13

    Answer: No, because the feature arrived in JDK 26, after JDK 25

    The title says final and the Release field says from when. A final feature of JDK 26 is not in JDK 25, so this track labels it JDK 26+.

  4. Question 4 of 13 When must you pass --enable-preview to use a preview language feature?

    Choose one answer.

    Show the answer: Question 4 of 13

    Answer: Both when compiling and when running

    A preview is off by default at both steps: you opt in when compiling, with the current --release, and again when running.

  5. Question 5 of 13 javac --release 24 --enable-preview on JDK 25 refused Status.java. Why?

    Choose one answer.

    Show the answer: Question 5 of 13

    Answer: Preview features are supported only for the current release, 25

    The option --enable-preview goes together only with the current release, because a JDK knows the preview features of its own release alone.

  6. Question 6 of 13 What did JDK 26 do to the stable values preview of JDK 25?

    Choose one answer.

    Show the answer: Question 6 of 13

    Answer: It renamed the API to LazyConstant and removed low-level methods such as orElseSet

    Stable values were renamed lazy constants in their second preview, in JDK 26, and are in their third preview in JDK 27.

  7. Question 7 of 13 A team's experiment uses a preview API of JDK 25. What must they expect when they move it to JDK 27?

    Choose one answer.

    Show the answer: Question 7 of 13

    Answer: To compile it again, and perhaps to rewrite it if the preview changed

    A preview is tied to one feature release. Moving to another means compiling again, and when the preview changed in between, as stable values did, rewriting the code too.

  8. Question 8 of 13 How can you recognise an incubator module by its name?

    Choose one answer.

    Show the answer: Question 8 of 13

    Answer: It starts with jdk.incubator, as in jdk.incubator.vector

    Incubating APIs live in modules whose names start with jdk.incubator. You add them with --add-modules, and the JVM warns when one is used.

  9. Question 9 of 13 Lanes.java printed WARNING: Using incubator modules: jdk.incubator.vector before its own output. Why?

    Choose one answer.

    Show the answer: Question 9 of 13

    Answer: JEP 11 requires a warning whenever an incubator module is used

    JEP 11 makes the JDK warn on every use of an incubator module, so nobody uses a changing API without knowing it.

  10. Question 10 of 13 Class files of which state carry the minor version 65535?

    Choose one answer.

    Show the answer: Question 10 of 13

    Answer: Preview

    Of the three states, only previews change the class file: minor version 65535, loadable only on the same feature release with previews enabled.

  11. Question 11 of 13 A feature is final only from JDK 27 on. Which label does this track give it, and will its example compile on JDK 25?

    Choose one answer.

    Show the answer: Question 11 of 13

    Answer: JDK 27+, and no, it does not compile on JDK 25

    JDK 26+ and JDK 27+ mark features that are final only from that release on and do not compile on JDK 25.

  12. Question 12 of 13 What does the lesson's JaCoCo example show about moving to a new feature release right away?

    Choose one answer.

    Show the answer: Question 12 of 13

    Answer: Some tools do not fully support it yet; JaCoCo 0.8.15 reads Java 27 class files only experimentally

    Right after a feature release, some tools may not support it yet: JaCoCo 0.8.15 reads Java 27 class files only experimentally, and Spring Boot 4.1.1 is documented as compatible with Java up to 26.

  13. Question 13 of 13 javac --release 21 on JDK 25 wrote out21/Recent.class. Which major version did javap report for it?

    Type a number.

    Show the answer: Question 13 of 13

    Answer: 65

    The release option writes class files for the release it names: version 65 for Java 21, which JDK 21 then ran. Without the option, JDK 25 writes version 69.

Interview questions

Fresher · What is the difference between a preview feature and an incubator module in Java?

A preview feature is a language, JVM or API feature that is fully specified and implemented but not yet permanent. It is off unless you compile with --enable-preview and the current --release, and run with --enable-preview; class files that use it are marked with minor version 65535 and run only on that same feature release. An incubator module holds a non-final API in a module whose name starts with jdk.incubator, such as jdk.incubator.vector; you add it with --add-modules, and the JDK prints a warning when it is used. Both exist to gather feedback before a feature becomes final. A preview usually becomes final after two or more rounds, or is removed; an incubating API moves to a preview or to a final module, and its packages are renamed on the way.

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Mid to senior

    A teammate wants to ship a service compiled with --enable-preview on JDK 25, because a preview API makes one part of the code much shorter. What risks do you raise, with evidence, and what would you suggest instead?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Senior to staff

    Your services run on Java 17. Management asks whether to move to Java 21 or straight to Java 25, and what it will take. How do you decide, and what do you check in the build, the tools and the dependencies before the first service moves?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A JEP’s title suffix and its Release field tell you whether a feature is final, a preview or an incubator, and in which JDK, and its status follows a fixed path from Draft to Closed / Delivered.
  • Preview features need --enable-preview at compile time (with the current --release) and at run time, and their class files carry minor version 65535 and load only on the same feature release.
  • Previews can change completely between releases: StableValue in JDK 25 became LazyConstant in JDK 26.
  • Incubator modules need --add-modules, print a warning, and get new package names when promoted, so shipped code uses final features only.
  • Teams build on an LTS because updates last years, tools take time to support new releases, and previews stay with their release, and javac --release N builds for an older release and checks its API too.

Terms in this lesson

--release option
The javac option that compiles for an older Java release: by that release's language rules, against its API and into class files of its version, so javac --release 21 writes version 65.
incubator module
A module whose name starts with jdk.incubator and that holds an API not yet ready for preview. It needs --add-modules, the JVM warns when it is used, and its packages are renamed when it is promoted.
JEP
A JDK Enhancement Proposal: the document that describes one change to the JDK, with its type, scope, status and the release that delivers it, at openjdk.org/jeps/ followed by its number.
preview feature
A fully specified and implemented feature that is not yet permanent. It needs --enable-preview to compile and to run, its class files carry minor version 65535, and a later release may change or remove it.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.