Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

If you learned Java around version 8 and have not followed later releases closely, much of the code you meet today will look unfamiliar, but it is still Java. Java 8 code is not invalidated, and the language has not been replaced. What has changed is the set of idiomatic ways to model data, restrict type hierarchies, branch on type, and write concurrent server code. This guide covers the representative milestones behind that shift, separates language syntax from platform APIs, and flags where a feature was still in preview or where the Java 25 material is still draft text.

Language changes and platform changes are different things

The title says “the language,” but the milestones fall into two groups. Records, sealed classes, and pattern matching for switch are language features: they change what the compiler accepts and how you write code. Virtual threads are a platform capability delivered through the runtime and core libraries. They change how thread-per-request server code can be built, but they do not add new grammar.

Keeping the two apart helps when you read older tutorials. A new library class does not require new syntax, and a new syntax form does not automatically make a program faster.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The milestones at a glance

Feature Release Category What changes for a Java 8 reader Status note
Record classes Java SE 16 Language A compact, dedicated form for classes that model data by their components Identified as a Java SE 16 feature in the OpenJDK-hosted Java Language Specification change document for record classes. Preview history not stated in the documents cited here.
Sealed classes and interfaces Java SE 17 Language A declaration restricts which types may directly extend or implement it Identified as a Java SE 17 feature in the OpenJDK-hosted Java Language Specification change document for sealed classes.
Pattern matching for switch Java SE 21 Language A switch can match on types and check coverage, which replaces many instanceof chains Evolved through preview stages in the Java SE 19 and 20 preview specifications. Check the final Java SE 21 specification for detailed rules.
Virtual threads JDK 21 (JEP 444) Platform Lightweight threads intended for thread-per-request server applications Finalized in JDK 21 according to JEP 444: Virtual Threads.
Compact source files and instance main methods Java SE 25 Language, aimed at small programs and learning code Relaxes the class and main-method requirements for small programs; the draft also refers to a companion module-import feature Draft JLS change document. Final status and exact rules not confirmed here; check the JDK 25 release documentation.

This table is representative, not a full inventory. It does not cover intermediate features such as lambdas, modules, local-variable type inference, text blocks, or sequenced collections. Read the release notes for each version you target to see the complete list.

Records: data classes without the ceremony

In Java 8, a class that only carries values usually needs a constructor, one accessor per field, and hand-written or generated equals, hashCode, and toString methods. A typical version looks like this:

public final class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() { return x; }
    public int getY() { return y; }

    // equals, hashCode and toString usually follow
}

A record expresses the same model in one line:

public record Point(int x, int y) {}

The record generates the canonical constructor, accessors named after the components (p.x(), not p.getX()), and equals, hashCode, and toString based on those components. Its fields are final.

A record is not a drop-in replacement for every class. It fits best when identity is defined by the data it carries. Classes with mutable state, inheritance needs, or behavior that depends on object identity are poor candidates, and older code that relies on those traits should not be rewritten mechanically.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Closed hierarchies: sealed classes

Java 8 has no way to say, “these are the only subtypes of this type.” An abstract class or interface can be extended by any class that can see it, so a switch or an instanceof chain over that type can never be proven complete. Sealed types add that guarantee:

public sealed interface Shape permits Circle, Square {}

public record Circle(double radius) implements Shape {}

public record Square(double side) implements Shape {}

The permits clause lists the permitted direct subtypes. Each permitted subclass must declare itself final, sealed, or non-sealed, and records are implicitly final, which is why they fit neatly here. The restriction applies to direct subtypes only; the rule about what each permitted subtype may do further down the hierarchy is part of the Java SE 17 specification and should be checked there for edge cases.

Branching on type: pattern matching for switch

Without sealed types or patterns, branching on the kind of an object looks like this:

static double area(Shape s) {
    if (s instanceof Circle) {
        Circle c = (Circle) s;
        return Math.PI * c.getRadius() * c.getRadius();
    } else if (s instanceof Square) {
        Square q = (Square) s;
        return q.getSide() * q.getSide();
    }
    throw new IllegalStateException();
}

The final throw is required because the compiler cannot know whether another subtype exists. With Java SE 21 pattern matching for switch and a sealed Shape, the same logic reads as follows:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
static double area(Shape s) {
    return switch (s) {
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Square q -> q.side() * q.side();
    };
}

Because Shape is sealed and both permitted subtypes are named, the switch covers every case and needs no default branch. The compiler can check that coverage, which is the practical point of combining the two features. Detailed rules for guards, nested patterns, and other edge cases are defined in the final Java SE 21 specification, not in this summary.

Virtual threads: a platform change for server code

Virtual threads are not a language feature, and they are not a shortcut for every concurrency problem. JEP 444: Virtual Threads, finalized in JDK 21, states its goal as follows:

“Enable server applications written in the simple thread-per-request style to scale with near-optimal hardware utilization.”

The JEP was authored by Ron Pressler and Alan Bateman, with Alan Bateman listed as owner. That sentence is a stated goal, not a measured result. This guide does not cite a benchmark for any workload.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A virtual thread is created and started through the Thread API or an executor:

Thread.ofVirtual().start(() -> handleRequest());

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

Several behaviors differ from platform threads, according to JEP 444, and should be understood before you adopt them:

  • Virtual threads are always daemon threads, so the JVM does not wait for them to finish before exiting.
  • Their priority is fixed at normal and cannot be changed.
  • They support thread-local variables, and the JEP describes how existing libraries can remain usable with them.
  • Their observability, meaning how they appear in tools and thread dumps, differs from platform threads.

Avoid summarizing the feature as “millions of threads for free” or as an automatic speedup. Whether it helps depends on how the application is structured and what it waits on.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Preview features: how to tell them apart and run them

A preview feature is fully specified but may change in a later release, and its use requires an explicit opt-in. Pattern matching for switch appeared in preview form in the Java SE 19 and 20 preview specifications before the Java SE 21 material, and record patterns followed a similar path. A snippet that compiles only with preview enabled should not be presented as ordinary Java.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To compile and run a preview example on the matching release, use both flags:

javac --release 21 --enable-preview Demo.java
java --enable-preview Demo

Classes compiled with preview features are marked and will not load without --enable-preview on the same release. If you see an error about preview features when running code that compiled, check that the run command includes the flag and that it targets the same JDK version used to compile.

Java 25: what is established and what to verify

The Java 25 material available for this topic is a draft Java Language Specification change document on compact source files and instance main methods. It refers to a companion module-import feature. A draft is not final normative text, so the details here are provisional. Before you teach or depend on this syntax, confirm three things in the JDK 25 release documentation: whether the feature is final or still preview, its exact syntax, and any changes made after the draft.

Updating your mental model

Use this checklist when you read unfamiliar code or older tutorials:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Check whether a construct is a language feature, a platform API, or a preview feature.
  • When you see a data class full of getters and boilerplate, ask whether it could be a record, and whether its identity really is its data.
  • When you see an instanceof chain over a type with a fixed set of subtypes, ask whether sealing and a switch pattern would let the compiler check coverage.
  • When a snippet relies on preview syntax, confirm the release and flags it needs.
  • For any Java 25 detail, check the final JDK 25 release documentation.

This guide does not decide whether a particular project should upgrade. Support timelines, migration cost, compatibility with existing dependencies, and project-specific needs were outside its scope, and those factors should drive any upgrade decision.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.