DEV Community

Cover image for From Java 8 to Java 25: the language you think you know no longer exists
Gaston Herrlein
Gaston Herrlein

Posted on

From Java 8 to Java 25: the language you think you know no longer exists

A journey through the history of Java.

1. Introduction — Java 8 Is Not Today's Java

If you learned Java with JDK 8, you probably know a version of Java that no longer fully represents the language today. For years, Java 8 was the standard: the version millions of developers used to learn lambdas, Streams, and functional programming for the first time. And it remains, to this day, one of the most widely used versions in production.

But Java didn't stop there. Since then, the language has gone through several important transformations:

Java 8 → Java 11 → Java 17 → Java 21 → Java 25
Enter fullscreen mode Exit fullscreen mode

This article doesn't aim to list every single new feature from each version — that would fill several books. The goal is more specific: to pick out the changes that best explain how Java has evolved as both a language and a platform, and why a developer who only knows Java 8 is missing out on an important part of what Java is today.

Java's evolutionary path


2. Before We Start: What Does a Java Version Actually Mean?

Before diving into specific features, it's worth clarifying a few terms that get used — and confused — constantly:

  • JDK (Java Development Kit): the complete package you need to develop in Java. It includes the compiler, tools, and the JVM.
  • JVM (Java Virtual Machine): the virtual machine that runs Java bytecode. It's the piece responsible for much of the language's performance, memory management, and portability.
  • Java SE (Standard Edition): the language specification and standard library that everything else is built on.
  • LTS versions vs. short-cycle versions: since Java 9, Oracle has released a new version every six months, but only some of them — Java 8, 11, 17, 21, 25 — get long-term support. The versions in between are stepping stones, useful for trying out preview features, but not meant to carry a project for years.
  • Release cycle: this six-month cadence, adopted starting with Java 9, is precisely what has allowed Java to evolve much faster than many developers realize, since almost no one closely follows the non-LTS releases.

Understanding this matters because a new Java version doesn't just mean "new reserved words." Here's the idea that will run through the rest of this article:

Java evolves simultaneously as a language, a platform, and a virtual machine.

The syntax changes, yes, but so do the APIs, the performance, the memory management, and the diagnostic tools. Ignoring any one of these three dimensions gives an incomplete picture of what has actually changed.


3. Java 8 — The Starting Point of Modern Java

Why It Mattered So Much

Java 8 (2014) is, without exaggeration, the version that redefined how Java is written. Before it, the language was deeply imperative and object-oriented in its most classic form. Java 8 introduced a functional vocabulary that, until then, belonged to other languages.

Features Worth Highlighting

  • Lambda expressions: functions as values, without needing anonymous classes.
  • Functional interfaces: the contract (Runnable, Function, Predicate...) that makes lambdas possible.
  • Stream API: a declarative way to process collections.
  • Optional: an explicit way to model the absence of a value.
  • java.time: a modern date API that replaced the problematic Date/Calendar.
  • Method references: a more compact way to refer to existing methods.
  • Default methods: interfaces that can now provide implementation, something unthinkable before Java 8.

Example

users.stream()
     .filter(User::isActive)
     .map(User::getName)
     .forEach(System.out::println);
Enter fullscreen mode Exit fullscreen mode

You don't need to dig into how Streams work internally to grasp what matters here: this snippet represents a paradigm shift. Instead of describing how to loop, filter, and accumulate with loops and intermediate variables, the code describes what you want to get.

'Traditional' approach vs. 'Lambda and Streams' approach

A Point to Reflect On

Java 8 introduces a new way of expressing operations on data, but it keeps much of the language's traditional verbosity. We were still writing full classes, constructors, getters, and equals/hashCode by hand. Java 8's revolution was functional, not syntactic in the broader sense.


4. Java 11 — Consolidation and Platform Evolution

Java 11 (2018), the next LTS release, is often described as "not very exciting" compared to Java 8 or what came after. And that's fair: from a syntactic standpoint, it's a much less disruptive evolution. But it would be a mistake to overlook it.

Features

  • New String methods: isBlank(), strip(), lines(), repeat().
  • var in lambdas: allows annotating lambda parameters with var, mostly useful for adding annotations.
  • HttpClient: a modern HTTP client, finally included in the JDK, with HTTP/2 support and an asynchronous API built on CompletableFuture.
  • Direct execution of .java files: java MyFile.java without needing to compile explicitly first.
  • Changes to JDK APIs and components: removal of modules like Java EE and CORBA, which fell outside the core of the language.

Example

String text = " Java 11 ";

text.isBlank();
text.strip();
text.lines();
Enter fullscreen mode Exit fullscreen mode

And a small conceptual example of the new HTTP client:

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://api.example.com/users"))
        .build();

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
      .thenApply(HttpResponse::body)
      .thenAccept(System.out::println);
Enter fullscreen mode Exit fullscreen mode

A Point to Reflect On

Here an important idea starts to emerge, one that will keep coming back throughout this article:

The evolution of Java doesn't only consist of changing the language's syntax.

The standard library and the platform itself evolve too. Having an HttpClient included out of the box, or being able to run a Java script without compiling it manually, doesn't change how you write a class — but it does noticeably change the day-to-day experience of working with Java.


5. Java 17 — Java Starts Cutting Down on Verbosity

Java 17 (2021) is one of the most important versions since Java 8, and arguably the one that marks the beginning of "modern Java" as it's understood today.

Features

  • Records: a concise way to declare immutable data-carrying classes.
  • Sealed classes: class hierarchies that are closed and controlled by their own author.
  • Pattern matching for instanceof: eliminates the explicit cast after checking a type.
  • Text blocks: readable multi-line strings, without concatenation or \n everywhere.
  • Evolution of switch: more expressive switch expressions, paving the way for what would arrive in Java 21.

Main Example

Traditional class:

public class User {
    private final String name;
    private final int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) { /* ... */ }

    @Override
    public int hashCode() { /* ... */ }

    @Override
    public String toString() { /* ... */ }
}
Enter fullscreen mode Exit fullscreen mode

Compared to:

public record User(String name, int age) {}
Enter fullscreen mode Exit fullscreen mode

Constructor, getters, equals, hashCode, and toString are all generated automatically, with the same immutability semantics you used to have to write by hand.

A Point to Reflect On

By now the reader should start to notice a clear trend:

less code → more expressiveness → clearer intent.

A record isn't just "fewer lines": it immediately communicates that this class is simply an immutable data carrier. The code itself starts to document its own intent.


6. Java 21 — The Leap Toward Modern Java

If Java 17 started cutting down on verbosity, Java 21 (2023) takes the leap that many consider the most important since Java 8. It's no coincidence that it's also LTS.

Main Features

  • Virtual Threads: lightweight threads managed by the JVM, with a creation and blocking cost radically lower than that of traditional platform threads.
  • Pattern Matching for switch: switch as a full expression, capable of discriminating by type and structure.
  • Record Patterns: decomposing records directly into patterns within switch or instanceof.
  • Sequenced Collections: a common interface for collections with a defined order, with direct access to the first and last element.
  • Concurrency improvements: the foundation of Project Loom, which underpins everything above.

Main Example

Thread.startVirtualThread(() -> {
    processRequest();
});
Enter fullscreen mode Exit fullscreen mode

The point isn't simply to create threads in a different way, but to enable a far more scalable concurrency model for certain kinds of applications. A traditional server handling thousands of concurrent requests needs, with platform threads, to carefully manage a limited pool of operating-system threads. With Virtual Threads, the JVM can multiplex millions of lightweight threads over a much smaller number of real threads, without developers needing to change how they program: the code remains blocking and sequential, but it stops being expensive.

Comparison of the traditional model versus virtual threads

This change pairs well with something that often goes unnoticed: much of the improvement doesn't happen only in the language, but in the JVM itself.


7. What Was Happening Inside the JVM?

This section is intentionally short. It's not meant to teach how the JVM works internally, but to prevent the article from giving the impression that Java has only changed its syntax.

While the language was gaining records, pattern matching, and Virtual Threads, the JVM was also moving forward in parallel:

  • Garbage Collector evolution: from a single general-purpose collector to several specialized alternatives (G1, ZGC, Shenandoah), each designed for different latency and heap-size profiles.
  • JIT improvements: the just-in-time compiler optimizes hot code better with every version.
  • Memory management: more efficient heaps with lower per-object overhead.
  • Performance and startup: reduced startup time and memory footprint, especially relevant in containers and serverless architectures.
  • Observability and diagnostic tools: JDK Flight Recorder (JFR) increasingly integrated and with less impact on production application performance.

The question this section answers is:

What has changed in Java even if we don't modify a single line of code?

The answer is: quite a lot. The exact same program, compiled the exact same way, can start faster, use less memory, and deliver better performance simply by running on a more modern JDK. Upgrading the JDK isn't only about new language features; it's often a free performance improvement too.

In-depth diagram: JDK, JVM, JIT, GC, JFR, OS


8. Java 25 — The Current State of Java LTS

Java 25 is the most recent LTS release, launched on September 16, 2025, two years after Java 21.

The Goal of This Section

This isn't about listing every new feature — Java 25 shipped with 18 JEPs spanning permanent, preview, incubator, and experimental features — but about showing where Java keeps evolving after the leap taken in version 21.

Stable Features Worth Knowing About

  • Compact Source Files & Instance Main Methods (JEP 512): Java programs without the traditional ceremony of a public class and a static main.
  • Compact Object Headers (JEP 519): smaller object headers in the JVM, with the resulting memory savings in applications with many objects.
  • AOT improvements: command-line ergonomics and ahead-of-time method profiling (JEP 514 and JEP 515), aimed at reducing startup time.
  • JFR/monitoring improvements: JFR Method Timing & Tracing (JEP 520), which extends diagnostic capability without needing external tools.
  • API and tooling evolution: among others, Module Import Declarations (JEP 511) and Flexible Constructor Bodies (JEP 513), which relaxes where code can go before the call to super().

A good example of where this version is heading is Compact Source Files. The traditional:

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello Java");
    }
}
Enter fullscreen mode Exit fullscreen mode

can now be written, for simple programs, in a much more direct way — without an explicit class or the mandatory modifiers at the entry point. Java 25 made this feature permanent after several earlier versions in preview.

This is interesting because it points to another direction Java is evolving in, different from the boilerplate reduction we saw with records:

making the language progressively more accessible without giving up the capabilities needed for complex applications.

Java isn't simplifying its core at the expense of its advanced capabilities; it's lowering the entry barrier for beginners, without touching what large, complex systems need.


9. Java 8 vs. Modern Java — One Feature, Five Generations

Let's take a simple problem and follow it across all five versions:

Get the active, adult users, and return their names.

Java 8
   ↓
Java 11
   ↓
Java 17
   ↓
Java 21
   ↓
Java 25
Enter fullscreen mode Exit fullscreen mode

In Java 8, the problem is already solved quite reasonably thanks to Streams:

List<String> names = users.stream()
        .filter(User::isActive)
        .filter(u -> u.getAge() >= 18)
        .map(User::getName)
        .collect(Collectors.toList());
Enter fullscreen mode Exit fullscreen mode

In Java 11, the solution to the problem itself barely changes; what changes is the environment: if those users came, say, from an external API, we'd now have HttpClient built right into the JDK to fetch them.

In Java 17, if User is modeled as a record, the data definition itself becomes much more compact, even though the filtering and mapping logic with Streams stays essentially the same:

public record User(String name, int age, boolean active) {}
Enter fullscreen mode Exit fullscreen mode

In Java 21, if this operation were part of a service handling thousands of concurrent requests, the improvement wouldn't be in the Stream line itself, but in how each request runs: on Virtual Threads instead of platform threads.

In Java 25, if this were a small utility script, we could take advantage of Compact Source Files to write it without the ceremony of an explicit class and main.

A new version doesn't necessarily have to change the code. In fact, that can be pedagogically interesting:

Sometimes a new version doesn't need to change how we write a given piece of functionality; it simply provides better tools for other problems.


10. A Cross-Cutting View: What Has Really Changed?

Rather than going back over each feature one by one, it's more useful to group them by the goal they serve:

Evolution Examples
More expressive code Lambdas, Records, Pattern Matching
Less boilerplate Records, new language constructs
Better data manipulation Streams
Better concurrency Virtual Threads
Better performance JVM evolution, GC, AOT
Better observability JFR and JVM tooling
Greater accessibility Compact Source Files

Seen this way, Java's evolution stops looking like a scattered list of features and starts reading as a coherent direction. The question left hanging is:

Is Java turning into a different language, or simply into a more modern Java?

Probably the latter. Nothing we've covered breaks with the principles that made Java what it is — strong typing, backward compatibility, portability. What's changed is how much code it takes, and how many tools are available, to express those same principles.


11. Which Version Should a Developer Try?

There's no single universal version to recommend to everyone. It makes more sense to match each version to a specific situation.

Java 8

For:

  • Understanding legacy code.
  • Maintaining existing projects.
  • Understanding the foundation modern Java evolved from.

Java 11

For:

  • Getting to know an intermediate stage in the language's evolution.
  • Understanding the bridge between Java 8 and modern Java.
  • Working with projects that still run on this generation.

Java 17

For:

  • Learning modern Java from a solid base.
  • Getting to know Records, Sealed Classes, and Pattern Matching.
  • Working with a widely established ecosystem with great library support.

Java 21

For:

  • Learning modern concurrency.
  • Experimenting with Virtual Threads in real applications.
  • Getting to know one of the biggest transformations in current Java.

Java 25

For:

  • Trying out the most recent LTS release.
  • Understanding where the language keeps heading.
  • Exploring modern JVM improvements, performance, and diagnostic tools.

The recommendation is contextual. It's not:

"Always use Java 25."

It's more about knowing where your project stands on this timeline, and deciding from there which version is worth exploring next.


12. Conclusion — Learning Java Shouldn't End at Java 8

Knowing Java 8 means knowing a fundamental part of Java, but it's not the same as knowing modern Java.

Readers who already master Java 8 have an excellent foundation to build on, but they should gradually experiment with Java 17, 21, and 25 to discover how the language has changed since then.

A simple way to frame that journey:

  • Java 8 → understand the foundation.
  • Java 17 → understand modern Java.
  • Java 21 → experiment with modern concurrency.
  • Java 25 → get to know the current state of the LTS ecosystem.

Conceptual map of the evolution of Java

You don't need to abandon a version that's working right away. But it is worth building small personal projects with modern versions of Java. Many of the features that at first seem like just "new syntax" end up changing how we think about and design our applications.

Top comments (0)