Languages: what modern Java looks like
November 12, 2026
This is an outline, not a finished note. The background it starts from is the Java on the work page: older Jakarta EE, and Spring Boot. The point of writing it down is to have a clear picture of the language as it is now, before later notes set the same ideas next to Go and Rust.
Where a line says TODO, that part is still to be written from my own reading and from code I actually have in front of me.
Why this note exists
The language moved, in public, through a series of JEPs. A lot of service code still looks like Java 8: classes with getters, loops, and a classpath of jars. This note is the list of changes I want to be able to explain without looking them up.
TODO: write down the language level and the frameworks in the code I actually work in, from the build files, rather than from memory.
Records
A record (JEP 395, Java 16) is a class whose state is the list of components in the header. The compiler writes the constructor, the accessors, equals, hashCode, and toString.
TODO: a before and after against a small immutable type I would otherwise have written by hand in a JEE or Spring service.
TODO: where a record is the wrong tool. Mutable state, and a type that needs a superclass, are the cases I want a sentence on.
Sealed types
A sealed class or interface (JEP 409, Java 17) names the types that are allowed to extend or implement it. The set of subtypes is closed.
TODO: a small hierarchy, a result or a message, with the permitted subtypes written out.
TODO: leave a pointer here for the later comparison with a Rust enum and with the usual Go approach of an interface plus unexported implementations. That comparison belongs in a later note. It is not written yet.
Pattern matching
Pattern matching for instanceof (JEP 394, Java 16) binds the tested value, so the cast after the test goes away. Pattern matching for switch (JEP 441, Java 21) lets a switch cover types.
TODO: one example that switches on a sealed type, and what the compiler reports when a permitted subtype is missing from the switch.
Text blocks and var
A text block (JEP 378, Java 15) is a multiline string literal. var (JEP 286, Java 10) infers the type of a local variable from its initializer. Neither one changes the type system. They change how a method reads.
TODO: paste a SQL or JSON literal I would otherwise have built with concatenation, and mark one
varI would keep and one I would write out in full.
Virtual threads
Virtual threads (JEP 444, Java 21, Project Loom) are threads scheduled by the JVM onto a smaller set of carrier platform threads. The style they are for is blocking I/O, with a thread per request, at a cost closer to an asynchronous runtime than to the old thread pool.
TODO: after reading the JEP, write what changes for a Spring Boot or servlet service that blocks on I/O, and what stays the same. Pin the notes on
synchronized, thread locals, and pooling to the JEP, not to a summary from memory.
TODO: this is the comparison point for goroutines. Leave the comparison until the Go note exists. The decision overview is Go or Rust.
The module system
The Java Platform Module System (JEP 261, Java 9) adds modules with requires and exports. A lot of Spring Boot and older JEE code still ships as a classpath of jars. "Module" in a Maven build is a different thing from a JPMS module.
TODO: state whether any project I touch builds with
module-info.java. If the only modules in sight are Maven modules, say that in one sentence.
Streams
The Stream API arrived in Java 8. It is the oldest item on this list, and the one most likely to already be in a codebase that otherwise looks like Java 8. A stream is a pipeline over a source: intermediate operations, then a terminal operation.
TODO: one loop I would rewrite as a pipeline, and one loop I would leave as a loop, taken from code I can point at.
What comes next
The next note in this series is the decision overview, Go or Rust: deciding which to commit to. It is about which language to spend the time on. It is not yet the feature-by-feature comparison.
Kafka on OpenShift is a separate note, Kafka within OpenShift.
TODO: a later note that takes records, sealed types, and virtual threads and sets them next to Go and Rust, once I can write the other side from the language rather than from a glossary.