Java is the Rodney Dangerfield of programming languages: it gets no respect. Mention Java to most developers under 30 and they'll picture XML configuration files, enterprise beans, and AbstractSingletonProxyFactoryBean. The language's reputation is frozen in 2008, when Java 6 was the latest version and writing Java meant drowning in boilerplate.
But something remarkable has happened since Java switched to a six-month release cadence in 2017. The language has been improving at a pace that would have been unimaginable during the four-year gaps between Java 6, 7, and 8. Records. Sealed classes. Pattern matching. Virtual threads. String templates. Structured concurrency. Each release adds features that make Java code shorter, more expressive, and more pleasant to write. Java 26 continues this streak, and the gap between 'Java as it is' and 'Java as people imagine it' has never been wider.
The Features That Changed Everything
If you haven't written Java since Java 8, you're missing a decade of improvements. Here's what the language looks like now.
Records (Java 14) are immutable data classes. What used to require 50 lines of boilerplate — fields, constructor, getters, equals, hashCode, toString — is now one line.
// Java 8: 50 lines of boilerplate
public 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; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Point)) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}
@Override
public int hashCode() { return Objects.hash(x, y); }
@Override
public String toString() { return "Point[x=" + x + ", y=" + y + "]"; }
}
// Java 14+: one line
record Point(int x, int y) {}
Pattern matching (Java 16-21, still evolving) lets you destructure objects in switch expressions and instanceof checks. This eliminates the cascading if-else-instanceof chains that plagued Java code.
// Pattern matching with sealed types and records
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}
// Exhaustive pattern matching — compiler verifies all cases covered
double area(Shape shape) {
return switch (shape) {
case Circle(var r) -> Math.PI * r * r;
case Rectangle(var w, var h) -> w * h;
case Triangle(var b, var h) -> 0.5 * b * h;
};
}
Virtual threads (Java 21) are lightweight threads managed by the JVM instead of the OS. You can create millions of them without running out of memory or OS thread handles. This makes the 'one thread per request' model — the simplest concurrency model — practical at scale.
// Create a million concurrent tasks — try this with OS threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 1_000_000).forEach(i -> {
executor.submit(() -> {
// Each task gets its own virtual thread
// Blocking I/O (HTTP, DB, file) automatically yields
var response = httpClient.send(request, BodyHandlers.ofString());
return response.body();
});
});
}
// All done — executor auto-closes, waits for completion
Virtual threads are Java's answer to Go's goroutines and Kotlin's coroutines. The difference: you don't need a special syntax or color your functions. Regular blocking code works — the JVM transparently suspends and resumes virtual threads at blocking points. Your existing blocking HTTP client, JDBC driver, and file I/O code benefits without modification.
Why Nobody Notices
Java's improvement trajectory is genuinely impressive. So why is the perception still stuck in 2008?
First, enterprise inertia. Many organizations run Java 8 or 11 because upgrading a large codebase is expensive and risky. When developers talk about 'Java,' they're talking about the version their company uses, not the latest release. Java 8 developers haven't experienced records, pattern matching, or virtual threads.
Second, the framework layer. Spring Boot, Jakarta EE, and other enterprise frameworks add their own complexity on top of the language. Developers blame 'Java' for Spring's annotation ceremony, but that's the framework, not the language. Modern Java without a heavy framework is surprisingly lean.
Third, cultural momentum. Tech culture assigns languages to tribes. Java is the 'enterprise' language, associated with large corporations, bureaucratic processes, and over-engineering. This reputation was earned — circa-2005 Java development deserved the criticism. But reputations outlast their causes by years.
The JVM Advantage
The language improvements matter, but Java's deepest advantage is still the JVM. After three decades of optimization, the HotSpot JVM is one of the most sophisticated runtime environments ever built.
- JIT compilation. The JVM profiles running code and compiles hot methods to optimized native code. For long-running applications (servers, data processing), JVM performance often matches or exceeds C++ because the JIT can optimize for the actual runtime behavior, not just static analysis.
- Garbage collection. Modern GC implementations (ZGC, Shenandoah) provide sub-millisecond pause times even with terabytes of heap. The 'GC pauses kill latency' criticism hasn't been true for years — these collectors run concurrently with the application.
- Observability. JFR (Java Flight Recorder) provides production-safe profiling with near-zero overhead. You can profile CPU usage, memory allocation, lock contention, I/O latency, and GC behavior without slowing down the application.
- Ecosystem maturity. Maven Central has millions of packages. The build tools (Gradle, Maven) are well-understood. The testing ecosystem (JUnit, Mockito, Testcontainers) is excellent. The deployment story (containers, GraalVM native images) has matured significantly.
GraalVM and Native Images
Java's biggest weakness has always been startup time. The JVM takes hundreds of milliseconds to initialize, load classes, and warm up the JIT compiler. For serverless functions and CLI tools, this overhead is unacceptable.
GraalVM Native Image addresses this by compiling Java applications ahead-of-time into standalone executables. No JVM needed. Startup time drops from hundreds of milliseconds to single-digit milliseconds. Memory usage drops because there's no interpreter, no JIT compiler, and no class metadata to load.
The trade-off: native images lose the JVM's adaptive optimization. A JIT-compiled application gets faster over time as the JVM learns the hot paths. A native image starts fast but doesn't improve. For short-lived processes (CLI tools, serverless), native images win. For long-running servers, the JVM's JIT eventually produces faster code. Pick the right tool for your runtime characteristics.
Modern Java vs. the Alternatives
The languages that 'replaced' Java — Go, Kotlin, Rust — are excellent. But modern Java competes more effectively than people realize.
Go's simplicity is appealing, but Java now has virtual threads (matching goroutines) and a much richer type system. Type systems matter for large codebases, and Java's — especially with sealed types and pattern matching — catches errors that Go's interface system misses.
Kotlin improves on Java's syntax but runs on the same JVM. Most of Kotlin's advantages over Java 8 (null safety, data classes, coroutines) have partial or full equivalents in modern Java (Optional patterns, records, virtual threads). Kotlin is still more concise, but the gap has narrowed significantly.
Rust occupies a different niche — systems programming without a garbage collector. Java shouldn't compete there. But for server-side applications, data processing, and enterprise software, Java's combination of performance, safety, and ecosystem maturity is hard to beat.
The Six-Month Cadence Is the Real Innovation
More than any single feature, the six-month release cycle changed Java's trajectory. Before 2017, Java releases were three-to-four-year events that tried to ship everything at once. Features got delayed because they weren't ready, which delayed the entire release, which created pressure to ship everything in the next release, which delayed that release. Java 9 took over three years partly because of the module system's complexity.
With six-month releases, features ship when they're ready. Pattern matching has been delivered incrementally across Java 16 through 24, with each release adding one piece. Virtual threads went through two preview releases before final delivery. This incremental approach lets the Java team gather real-world feedback and adjust designs before committing to permanent API decisions.
The lesson applies beyond Java: small, frequent releases compound into transformative change. Each individual Java release since 17 has been modest. Taken together, they've reinvented the language. If you tried Java five years ago and bounced off the verbosity, try it again. You might be surprised at what you find.