Java 26 is boring, and that’s a good thing

A joint article with Lutske de Leeuw.

When people hear “boring tech”, they usually mean old, slow, or not innovative. But in production, boring implies something very different. Boring means predictable, with no surprises. Boring means your system still works at 3 a.m. when nobody wants to debug a memory leak. And boring also means that you can understand your system years after you wrote it.

Many platforms try to impress developers with significant changes, shiny rewrites, or breaking updates. Java took another path. Java optimizes for trust.

That choice has a cost. Java can look conservative next to languages that ship new syntax, stronger type-system guarantees, or more ambitious standard-library features, much faster. From the outside, that can make Java seem like it is standing still, even as the platform improves underneath.

If a Java release feels boring, that usually means:

  • Your code still compiles
  • Your APIs will still work
  • Your upgrade does not turn into a rewrite project

And that’s not a weakness. That’s why Java has survived for decades

TL;DR: Java 26 is usefully boring

If you only remember one thing: Java 26 is not flashy, but it quietly improves the runtime and platform where it matters.

  • JEP 522 (G1 throughput): apps can get faster (often 5-15%) without code changes.
  • JEP 516 (AOT object caching with any GC): better startup behavior, especially for microservices.
  • JEP 517 (HTTP/3): modern protocol support in the standard client, with fallback.
  • JEP 500 (final means final): fewer reflection hacks, more predictable behavior.
  • JEP 504 (remove applets): old, dead tech finally cleaned up.
  • JEP 524, JEP 525, JEP 526, JEP 529, JEP 530: previews/incubator maturing steadily.

Java is Boring by Design

This “boring” story is not new. It is close to Java’s original DNA from 1995:

Original design goalWhy it still matters
Simple, object-oriented, and familiarTeams can keep large systems understandable for years
Robust and secureEnterprise software needs predictable failure modes
Architecture-neutral and portableJava still runs nearly everywhere
High performanceModern JVM work keeps improving performance release after release
Interpreted, threaded, and dynamicConcurrency and runtime behavior were core concerns from day one

If you look back at the different Java versions, the language changes since Java 8 have mostly been evolutionary rather than revolutionary.

var, records, text blocks, switch expressions, and pattern matching make Java easier to use, but they do not fundamentally change how most teams write software. That can make Java look less ambitious than newer languages.

At the same time, many newer platform features have their biggest immediate impact in libraries and frameworks, even if some, such as virtual threads, can also matter directly to application developers.

When library authors get better tools, everyone else gets better foundations for their applications. That is part of why Java can afford to stay boring.

A lot of Java’s practical innovation happens first in libraries, frameworks, build tools, and community spaces rather than in the language grammar itself. Java User Groups, conference talks, blog posts, and open-source experiments often act as the platform’s proving ground. Ideas get tested in the community long before they are standardized in the JDK. If you want to understand why Java keeps moving without constantly reinventing itself, look not just at the language, but also at the ecosystem around it, the global JUG network, and all the many conferences.

Even old Java looks Modern

For example, take:

import java.util.*;

class Example {
    Map<String, List<Set<Map<Integer, String>>>> complex;
    List<? extends Number> wildcardExtends;
    List<? super Integer> wildcardSuper;
}

Can you guess the Java version? It’s Java 5.

Or take this later snippet:

class Example {
    record Pair<T, U>(T first, U second) {}

    Pair<String, Integer> pair = new Pair<>("test", 42);
}

It’s Java 16, released in 2021. Most developers are surprised by how old common Java syntax really is. That is exactly the point. Johannes built the Java Version Game so you can test this yourself: try to guess which Java version introduced a piece of syntax, and you’ll quickly see how much of the language has been “boring” for far longer than you’d expect.

How to upgrade

For many projects, upgrading from Java 25 to Java 26 is straightforward at the language level. In the simplest case, it starts with updating the Java release version in your build configuration:

In Maven, you most often just have to change:

<properties>
   <maven.compiler.release>26</maven.compiler.release>
</properties>

Or in Gradle:

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(26)
    }
}

Modern IDEs typically highlight deprecated or removed APIs and suggest alternatives where needed. Thanks to Java’s strong backward compatibility guarantees, many applications compile and run without source changes, making the upgrade feel closer to routine maintenance than to a migration project.

That said, real upgrades are rarely just about the compiler. Teams also need to check framework support, build plugins, annotation processors, CI images, container base images, and the versions of libraries they depend on. In other words, the Java code may upgrade cleanly while the surrounding delivery stack still needs attention.

Backward compatibility should not be an excuse never to upgrade. “It still works on Java 8” is often technical debt with a smiley face. You might avoid short-term work, but you pay with missed security updates, missed runtime performance gains, and an increasingly fragile dependency graph.

Johan Vos put it well:

“There is a small but immediate cost of upgrading. There is a huge, potentially catastrophic but not immediate cost of staying on old versions. Until the disasters become visible, people don’t want to invest. Sounds a bit like climate change.”

If your codebase is large, tools like OpenRewrite can automate repetitive migration tasks, significantly reducing upgrade effort.

Important: Only use non-LTS versions of your JDK if you are aware of the risks and willing to upgrade twice a year. Never use these releases in production. But please still try them out, give feedback to the JDK developers, and report bugs.

What Java 26 changes

Java 26 introduces many JEPs (Java Enhancement Proposals), but not all have the same impact on day-to-day development. Some changes affect how Java behaves at runtime or how you interact with core APIs. Others are previews or incubating features that are intentionally not final yet.

Honestly, the most important practical improvement in Java 26 is not in the language or its APIs. It is the improved throughput with the G1 garbage collector. In other words, an upgrade can just give you better performance. This G1 improvement is the best kind of change, which is also the best kind of optimization: the kind where you do almost nothing.

In this section, we focus on the changes most likely to matter to Java developers today. These are features that are enabled by default, influence performance or behavior, or represent a clear step forward for the platform.

Preview and incubating features are treated differently. They are still evolving, require explicit opt-in, and may change or even be removed in future releases. Because of that, they are summarized later in this article, with a short explanation for each JEP. The goal there is awareness, not immediate adoption.

The following paragraphs highlight the most concrete changes in Java 26. After that, we take a short look at the preview and incubating features.

G1 GC: Improve Throughput by Reducing Synchronization (JEP 522)

The Garbage-First collector (G1) is the default garbage collector, so its performance is critical. Ivan Walulya and Thomas Schatzl improved G1 by reducing the synchronization required between application and GC threads. The main result is better throughput, with possible latency benefits depending on the workload.

In the JEP’s benchmarked workloads, throughput improved by 5-15% without application changes (source). Real applications will vary, but this is still the best kind of improvement: the platform gets faster while your code stays the same. It also reflects a platform that spends significant engineering effort on the JVM itself, not just on surface-level language changes.

This is arguably the most important change in Java 26, and also the most “boring” one: meaningful performance gains without changing your application code.

Ahead-of-Time Object Caching with Any GC (JEP 516)

JEP 516 is another practical runtime improvement that is easy to miss. The idea is that the JVM can cache startup objects from a training run and reuse them on future starts. In Java 26, this is available with any garbage collector, not only G1.

# Training run: record startup-created objects
java -XX:AOTCacheOutput=app.aot -jar myapp.jar

# Later starts: load prebuilt cache
java -XX:AOTCache=app.aot -jar myapp.jar

This can improve startup behavior for microservices and containerized workloads, and it does so without requiring application code changes

Prepare to Make Final Mean Final (JEP 500)

JEP 500 addresses a long-standing inconsistency: final fields that can still be mutated via reflection hacks. Java 26 starts warning about this behavior and prepares the ecosystem for stricter enforcement in a future release.

// This works in Java 25, but will warn in Java 26 and eventually be disallowed
class Config {
    private final String secret = "original";
}

var config = new Config();
var field = Config.class.getDeclaredField("secret");
field.setAccessible(true);
field.set(config, "hacked"); // <- final? Not really.

Example warning you can expect in Java 26:

WARNING: Final field mutation via reflection is deprecated and will be disallowed in a future release
WARNING: Attempted to mutate final field Config.secret using java.lang.reflect.Field::set

Historically, libraries used tricks like this for serialization, proxies, or framework internals. The platform is now moving toward stronger guarantees, which improve predictability and optimization opportunities.

HTTP/3 in the standard HTTP client (JEP 517)

Java 26 adds support for HTTP/3 to the standard java.net.http.HTTPClient API. This allows Java applications to communicate with HTTP/3 servers using the same client that was introduced in Java 11, without requiring a new API or a rewrite. Support for HTTP/3 is explicitly opt-in and does not change the default protocol behavior.

Why HTTP/3 matters today?

HTTP/3 is the successor to HTTP/2 and is built on top of the QUIC transport protocol rather than TCP. QUIC is a modern transport protocol designed for web traffic and runs over UDP, with TLS 1.3 integrated by default. This design reduces connection setup time, avoids head-of-line blocking, and improves performance on unreliable or high-latency networks. HTTP/3 is already widely used by modern browsers and supported by a large portion of public web infrastructure, making it increasingly relevant for client-side applications.

What does it mean that HTTP/3 lives in JDK 26?

By including HTTP/3 support in the standard HTTP Client API, Java removes the need for third-party networking libraries to access modern web protocols. Existing application code remains essentially unchanged, and developers can opt in to HTTP/3 on a per-client or per-request basis. If a server does not support HTTP/3, the client transparently falls back to HTTP/2 or HTTP/1.1. This approach preserves backward compatibility while allowing applications to adopt newer protocols at their own pace.

If you want to try it, the API usage is intentionally simple:

var client = HttpClient.newBuilder()
    .version(HttpClient.Version.HTTP_3)
    .build();

var request = HttpRequest.newBuilder()
    .uri(URI.create("https://example.com/api"))
    .build();

var response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());

Do you need HTTP/3?

I asked a Netty developer, and his answer essentially was: “You’ll be fine without it. HTTP/2 is good enough.”

HTTP is often not the bottleneck, and HTTP/2 is usually good enough. It is still useful for Java to keep pace with newer protocols, even if many teams will only use HTTP/3 once their frameworks and infrastructure adopt it.

The end of Java Applets (JEP 504)

Java applets were once the way you implemented interactive web applications if you didn’t want to use Flash. As an example:

import java.applet.Applet;
import java.awt.Button;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class HelloApplet extends Applet {
    private String message = "Hello from an Applet";
    private int clicks = 0;

    @Override
    public void init() {
        Button button = new Button("Click me");
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                clicks++;
                message = "Clicks: " + clicks;
                repaint(); // trigger redraw
            }
        });
        add(button);
    }

    @Override
    public void paint(Graphics g) {
        g.drawString(message, 20, 40);
    }
}

You could embed this via:

<!doctype html>
<html>
  <body>
    <applet code="HelloApplet.class" archive="hello-applet.jar" width="300" height="120">
      Your browser does not support Java applets.
    </applet>
  </body>
</html>

Java 26 removes the Applet API. This is not a breaking change in practice, but the final step of a very long deprecation process.

Applets have been obsolete for years. Browser support had already collapsed after NPAPI disappeared, and Java followed with a long, explicit off-ramp: the Applet API was deprecated in Java 9 in 2017, the appletviewer tool was removed in Java 11, the API was deprecated for removal in Java 17, and Java 26 now removes it entirely. With the Security Manager permanently disabled, there is no longer any technical basis for running applets safely.

As a result, Java 26 removes the java.applet package, along with related classes such as JApplet. For most developers, this has zero impact. Code that was still dependent on applets was already tied to older Java versions. And modern alternatives have existed for a long, long time.

This change is a good example of Java’s careful evolution: once a core feature, the Java platform phased it out gradually with years of notice, clear migration paths, and a quiet removal when the API lost its relevance. This is how boring works.

Preview and incubating features

Beyond PEM, Java 26 includes several preview and incubating features. The short version is simple: they are interesting, they are opt-in, and most teams can safely watch them mature from a distance for now.

If you want to try them, you must enable previews explicitly:

javac --enable-preview --release 26 PrimitivePatternExample.java
java --enable-preview PrimitivePatternExample

The short version

JEPFeatureWhy it matters
JEP 530Primitive patternsMakes pattern matching more consistent by covering primitive types too.
JEP 526Lazy constantsStandardizes a common lazy-initialization pattern with proper safety guarantees.
JEP 529Vector APIGives performance-sensitive libraries explicit SIMD access.
JEP 525Structured concurrencySupport for the PEM encoding of cryptographic objects,
JEP 524PEMSupport for the PEM encoding of crypthographic objects,

The common theme is maturity over speed. Java is still exploring these ideas publicly with feedback, rather than forcing them into production too early.

Who these features are really for

Many of the newest platform capabilities primarily help library and framework developers first. That is often how Java improves safely.

Library authors useTypical effect for application teams
Vector APIFaster search, crypto, and data-processing libraries
Structured concurrencyCleaner, safer framework-level parallel orchestration
Memory/FFI improvementsBetter native integration in infrastructure libraries
Virtual-thread ecosystem supportHigher throughput with less thread-management pain
PEMDirect support of the PEM encoding in the standard library, so less boilerplate or external dependencies.

When those libraries improve, most teams benefit without having to rewrite business logic.

You don’t need these yet

Preview features are opt-in for a reason. They are there so developers can experiment, give feedback, and help shape the platform before these APIs become permanent. You can ignore them today and still get plenty of value from Java 26.

The same is true, more broadly, for the short release cadence. Non-LTS releases like Java 26 mean you do not have to wait years for useful features, runtime improvements, and ecosystem feedback to arrive. Even if you do not run every short-term release in production, they still help move the platform forward faster and make the eventual LTS releases better.

And if something here looks interesting, try it. That is part of how Java improves. Preview features exist so developers can kick the tires, find rough edges, and tell the platform engineers what works and what does not.

That community loop matters. Java is not built in a vacuum, and it is not shaped only by a small group behind closed doors. It is shaped by users, library authors, framework maintainers, JUGs, conference speakers, and people who try new things early enough to say, “this works,” “this is awkward,” or “please do not ship it like this.” That participatory culture is one of the reasons Java can move carefully without standing still

When boring is a liability

The argument for boring tech becomes much weaker when boring turns into visible lag.

Java still lacks or only partially supports several features that are standard elsewhere: built-in string interpolation, first-class null-safety, built-in JSON support, and simpler day-to-day concurrency ergonomics. Other languages often introduce these ideas to developers sooner, and developers benefit from a more modern look as a result.

That matters because language design is not only about raw capability. It also shapes how quickly new ideas spread, how much boilerplate teams accept, and whether new developers see a language as growing or merely enduring. Java’s cautious pace can therefore cost it mindshare, especially among developers comparing it with Kotlin, Rust, TypeScript, or newer JVM-adjacent tools.

Still, Java’s counterargument is stronger than “we do not need new things.” It is that some features are more valuable once they are proven, specified carefully, and integrated in a way that does not destabilize millions of existing systems. Java often arrives later, but it also tries to arrive with fewer surprises. That trade-off can be frustrating, but it is not irrational.

Java 26 improves a lot, but some long-requested ergonomics are still missing or only partially addressed:

  • built-in string interpolation, especially compared with the long-settled ergonomics of C# string interpolation,
  • first-class null-safety in the type system, as Kotlin demonstrates on the JVM,
  • built-in JSON support in the standard library, which many developers now expect out of the box,
  • simpler concurrency APIs and primitives for everyday code, where other platforms often expose structured concurrency more directly,
  • and probably many more.

Java is not perfect. We’re not here to pretend it is.

But none of this makes Java unusable. It does mean that Java sometimes pays a real price for its caution: Kotlin makes null-safety feel normal on the JVM, C# made string interpolation mundane years ago, many developers expect JSON handling to be built in rather than delegated immediately to Jackson or Gson, and structured concurrency often feels easier to reach for in ecosystems that expose it more directly. The upside is that Java usually adopts change with far more concern for compatibility than trendiness.

Conclusion

Well, Java 26 is boring, except for the JVM. So upgrading to the next JVM can still be worthwhile, even if the language itself has changed little.

One of Java’s great strengths is that you can still write code that looks a lot like Java 8 and nobody will notice. You can focus on your actual application instead of constantly rewriting your mental model whenever language fashions change.

Evolution over revolution is a big part of what kept Java relevant for 30 years. Careful language design and steady runtime work will likely keep it relevant for much longer.

Can Java be improved? Yes to all. Are some features still awkward? Yes. Does it miss things, such as string interpolation, that other languages already have? Also yes.

But Java has a vibrant community, its runtime is shaped by more than one company, and it still runs almost everywhere.

For a platform that is supposedly boring, that is a remarkably strong place to be. Java is still resilient, still improving, and still one of the safest bets you can make if you care about software that has to last.

It has never been a better time to be a Java developer. See you in another 30 years.

P.S.: If you want to see the talk version of this article, come to VoxxedDays Amsterdam 2026.

Authors

  • Johannes Bechberger

    Johannes Bechberger is a JVM developer working on profilers and their underlying technology in the SapMachine team at SAP. This includes improvements to async-profiler and its ecosystem, a website to view the different JFR event types, and improvements to the FirefoxProfiler, making it usable in the Java world. His work today comprises many open-source contributions and his blog, where he regularly writes on in-depth profiling and debugging topics. He also works on hello-ebpf, the first eBPF library for Java. His most recent contribution is the new CPU Time Profiler in JDK 25.

    View all posts
  • Lutske de Leeuw is a Software Engineer at Craftsmen and co-organizer of JUG Noord and ApeldoornJUG. A Java developer at heart and full-stack by trade. She loves to share knowledge through storytelling, often featuring animals like cats or llamas to make complex topics more relatable. She regularly speaks across Europe about AI and software craftsmanship. Lutske studied Computer Science and completed a Deep Learning specialization, and she enjoys experimenting with AI to bridge the gap between innovation and everyday development. Passionate about Devoxx4Kids and other knowledge-sharing initiatives, she believes learning should always be fun and accessible for everyone.

    View all posts

New posts like these come out at least every two weeks, to get notified about new posts, follow me on BlueSky, Twitter, Mastodon, or LinkedIn, or join the newsletter:

Leave a Reply

Your email address will not be published. Required fields are marked *