Java — Reference
Source: https://docs.oracle.com/en/java/javase/25/
Java
- Created: 1995 by James Gosling at Sun Microsystems; now stewarded by Oracle and the OpenJDK community
- Latest stable: JDK 26 (2026-03-17, non-LTS); JDK 25 (LTS) (2025-09-16) — recommended for production
- Paradigms: object-oriented (class-based), imperative; functional features since 8 (lambdas, streams); pattern matching since 21; records and sealed classes since 16/17
- Typing: static, nominal, mostly invariant generics with use-site variance via wildcards (
? extends,? super); type erasure at runtime - Memory: garbage collected. JDK 25 ships ZGC (generational, sub-millisecond pauses), G1 (default), Shenandoah, Parallel, Serial, Epsilon (no-op for benchmarking)
- Compilation: AOT to bytecode (
javac→.class) → JIT-compiled at runtime by HotSpot’s C1/C2 (or GraalVM). AOT-to-native via GraalVMnative-imageand Project Leyden. - Primary domains: enterprise backends, Android (Kotlin/Java on ART), big data (Hadoop, Spark, Flink, Kafka), build tooling, financial systems, embedded (Java ME), IDEs (IntelliJ, Eclipse, NetBeans)
- Official docs: https://docs.oracle.com/en/java/javase/25/
At a glance
- JDK distributions: Oracle JDK, OpenJDK builds (Eclipse Temurin / Adoptium, Amazon Corretto, Azul Zulu, Microsoft Build of OpenJDK, BellSoft Liberica, SapMachine, GraalVM, IBM Semeru). All built from OpenJDK source.
- Release cadence: 6-month feature releases (JEP-process driven), LTS every 2 years (8, 11, 17, 21, 25, …).
- JVM is multi-language: Kotlin, Scala, Clojure, Groovy, JRuby, Jython, plus polyglot via GraalVM (JS, Python, Ruby, R, WASM).
- Governance: OpenJDK Community + Java Community Process (JCP) for JSRs; JEPs (JDK Enhancement Proposals) drive features.
Getting started
Install:
- Recommended: SDKMAN! (
sdk install java 25.0.1-tem) — switches JDKs per shell. - Direct: Eclipse Temurin (https://adoptium.net), Microsoft Build of OpenJDK, Amazon Corretto, Oracle JDK.
- Windows:
winget install EclipseAdoptium.Temurin.25.JDK. macOS:brew install --cask temurin@25.
Hello world (single file, since JEP 330 / 477):
// Hello.java — runnable directly with `java Hello.java` (no compile step) since JDK 11+
void main() { // implicit class & no-arg main since JDK 25 (JEP 512)
IO.println("Hello, world!");
}Classic form:
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}Project layout (Maven / Gradle convention):
myapp/
pom.xml # or build.gradle(.kts)
src/
main/
java/
com/example/App.java
resources/
test/
java/
com/example/AppTest.java
target/ # Maven; build/ for Gradle
Build tools:
- Maven (XML, declarative, ubiquitous in enterprise) —
mvn package. - Gradle (Groovy or Kotlin DSL, programmatic) —
./gradlew build. Default for Android. - Bazel (Google’s, mono-repo).
- Newer: Mill, Bld.
- JDK 25 added the JEP 512 launcher with implicit imports; you can ship single-file scripts.
REPL: jshell (since JDK 9). Online: https://dev.java/playground/
Basics
Primitives: byte (8), short (16), int (32), long (64), float (32), double (64), char (UTF-16 code unit), boolean. Boxed: Integer, Long, Double, etc. Value classes (Project Valhalla) are landing in preview.
Variables / scope: lexically scoped. var (JDK 10+) for local-variable type inference (NOT for fields, params, returns). final for immutable bindings.
var users = new ArrayList<String>(); // inferred ArrayList<String>
final int max = 10;Control flow: if/else, for, enhanced for (T x : iterable), while, do/while, switch (statement and expression form), try/catch/finally, try-with-resources. Pattern matching for switch (JEP 441, JDK 21):
String describe(Object o) {
return switch (o) {
case Integer i when i > 0 -> "positive int " + i;
case String s -> "string: " + s;
case null -> "null!";
default -> "other";
};
}Functions: methods belong to classes. Lambdas (since 8) implement functional interfaces (one abstract method).
Function<Integer, Integer> square = x -> x * x;
List<String> upper = names.stream().map(String::toUpperCase).toList();Method references: Class::method, instance::method, Class::new.
Strings: String is immutable, UTF-16 internally (compact strings since 9 use Latin-1 when possible). Text blocks """...""" (JDK 15). String templates were withdrawn (JEP 459 retracted) — no native interpolation as of JDK 25; use String.format or formatted.
Built-in collections: List, Set, Map, Queue, Deque interfaces with ArrayList, LinkedList, HashMap, LinkedHashMap, TreeMap, HashSet, TreeSet, ArrayDeque. Immutable factories: List.of(), Map.of(), Set.of(). Sequenced collections (JDK 21): SequencedCollection, SequencedSet, SequencedMap with getFirst/getLast/reversed.
Intermediate
Type system:
- Generics with invariant type parameters; use-site variance via
? extends T(covariant) /? super T(contravariant). - Erased at runtime (no
T.classdirectly — passClass<T>token). - Bounded type params:
<T extends Comparable<T>>. - Records (JDK 16):
public record Point(int x, int y) {}— immutable data carriers with autoequals/hashCode/toString. - Sealed classes (JDK 17):
sealed interface Shape permits Circle, Square {}— exhaustive pattern matching. - Pattern matching for
instanceof(JDK 16):if (o instanceof String s) { ... }.
Modules / packages: package = directory; module = JEP 261 (JDK 9) module-info.java declaring requires/exports/opens. Most apps still use the classpath, not the module path — module system is mostly used by the JDK itself and by some frameworks.
Errors: checked vs unchecked exceptions — checked must be declared with throws or caught. RuntimeException and Error are unchecked. Try-with-resources auto-closes AutoCloseable.
try (var f = Files.newBufferedReader(path)) { ... } // auto-closeConcurrency primitives:
Thread,Runnable,Callable<V>,Future<V>,CompletableFuture<V>.java.util.concurrent:ExecutorService,ForkJoinPool,Semaphore,CountDownLatch,CyclicBarrier,ConcurrentHashMap,BlockingQueue.synchronized,volatile,Lock,ReentrantLock,StampedLock.- Virtual threads (JEP 444, JDK 21) — millions of cheap green threads on a small carrier-thread pool.
Thread.startVirtualThread(...),Executors.newVirtualThreadPerTaskExecutor(). - Structured concurrency (JEP 480, finalizing):
StructuredTaskScopefor parent-child task lifetimes. - Scoped values (JEP 446): immutable per-thread context, designed for virtual threads.
File I/O / networking: java.nio.file.Path/Files/FileChannel, java.net.http.HttpClient (HTTP/2 + WebSocket), Socket/ServerSocket, Selector for non-blocking I/O.
Stdlib highlights: java.time (JSR-310 immutable date/time), java.util.stream, java.util.function, java.lang.invoke (MethodHandles), java.util.concurrent, java.security, java.net.http, java.lang.foreign (Panama FFM, JDK 22).
Advanced
Memory model & GC:
- JMM (JLS Ch. 17) defines happens-before, volatile semantics, final-field freezing.
- Default GC: G1 (low-pause, regional). ZGC (generational since JDK 21, sub-ms pauses, multi-TB heaps). Shenandoah (Red Hat). Parallel (throughput-oriented). Serial (single-threaded). Epsilon (no-op).
- Tune:
-Xms/-Xmx(heap size),-XX:+UseZGC,-XX:MaxGCPauseMillis=N,-XX:NewRatio,-XX:SurvivorRatio. Inspect:-Xlog:gc*:file=gc.log. - Tools: JFR (Flight Recorder, free since 11), Mission Control (JMC),
jstat,jmap,jhsdb, async-profiler, Eclipse MAT.
Concurrency / parallelism deep dive:
- HotSpot biased locking removed in 15. Lightweight + heavyweight (monitor) locks remain.
VarHandle(JDK 9) — typed, mode-aware (getVolatile,compareAndSet,getAcquire/setRelease). Replaces mostUnsafeatomic uses.- Fork/Join uses work-stealing deques. Common pool sized to
Runtime.availableProcessors()-1. Inappropriate for blocking work — use a custom pool. - Virtual threads (JEP 444, JDK 21 GA): ~2 KB initial stack (vs ~1 MB platform thread), millions per JVM possible. Built on continuations (JEP draft). Carrier-thread pool defaults to
availableProcessors(). Virtual threads pin to carrier insidesynchronized(fixed in JDK 24, JEP 491) or native frames — preferReentrantLockin pinning-sensitive code on pre-24. Diagnose pins with-Djdk.tracePinnedThreads=full. - Structured concurrency (JEP 480/505):
StructuredTaskScope.<T>open()opens a scope,fork(callable)spawns child virtual threads,join()waits for all, scope close ontry-with-resourcescancels stragglers. Eliminates leaked futures. Joiners:awaitAll,awaitAllSuccessfulOrThrow,anySuccessfulResultOrThrow. - Scoped values (JEP 506, finalizing in 25/26): immutable per-thread context with structured lifetime — designed for virtual threads where
ThreadLocalis wasteful.ScopedValue.where(KEY, value).run(() -> ...). - Vector API (incubator since 16, ~ninth round in 25, targeting Valhalla finalization): SIMD intrinsics —
FloatVector.fromArray(SPECIES_512, src, 0).mul(...)lowers to AVX-512 / NEON.
FFI / interop:
- Project Panama Foreign Function & Memory API (
java.lang.foreign, finalized JDK 22) — modern replacement for JNI.Arena,MemorySegment,Linker,FunctionDescriptor.jextractgenerates bindings from C headers. - JNI still works but is verbose and unsafe.
- GraalVM Polyglot API: call JS, Python, Ruby, WASM from Java.
Reflection: java.lang.reflect (Class, Method, Field, Constructor, Modifier). MethodHandles (java.lang.invoke) — faster, type-safe, supports invokedynamic. Records expose RecordComponent[].
Performance tuning:
- JIT: HotSpot tiered compilation (Interpreter → C1 with profiling → C2).
-XX:+PrintCompilation,-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly(needs hsdis disassembler plugin). Inspect inlining:-XX:+PrintInlining. - JFR:
-XX:StartFlightRecording=duration=60s,filename=app.jfr. Open in JMC (Java Mission Control). Stream events live in 21+ viaRecordingStream. Overhead under 1% — safe in prod. - Profilers: async-profiler (JFR-aware, AsyncGetCallTrace, no safepoint bias, default for serious JVM work), VisualVM, YourKit, JProfiler. Honest Profiler for old JVMs.
- Microbenchmarks: JMH — the only correct way to benchmark JVM code. Auto-handles warmup, deoptimization, blackholes, prevents JIT from eliminating dead code via
Blackhole.consume(x). - AppCDS / CDS (Class Data Sharing): pre-share class metadata (
-XX:ArchiveClassesAtExit=app.jsa, then-XX:SharedArchiveFile=app.jsa). 30-50% faster startup on small services. - CRaC (Coordinated Restore at Checkpoint, JEP 483 / OpenJDK CRaC project): snapshot a running JVM to disk, restore in milliseconds. Used by AWS Lambda SnapStart, OpenLiberty, Quarkus, Spring Boot 3.2+. Cold start: 4s → 40ms typical.
- AOT: GraalVM
native-image(closed-world, fastest start), Project Leyden (JEP 483 AOT class loading + linking + AOT method profiling cache + eventual native compilation without closed-world). Leyden’s-XX:AOTMode=recordthen=createthen=onworkflow lands in 25+. - GC choice quick guide: G1 default for most apps (heap < 32 GB, throughput + low pause), Generational ZGC (heap multi-TB, sub-ms pauses, slight throughput cost), Shenandoah (Red Hat, similar to ZGC, lower memory overhead), Parallel (max throughput, longer pauses, batch jobs), Serial (containers <100 MB).
God mode
Bytecode (javap, ASM, ByteBuddy):
javap -p -c -v Foo.class # disassembleBytecode is stack-based, ~200 opcodes (iload, invokevirtual, invokedynamic). ASM is the low-level lib; ByteBuddy is the friendly wrapper used by Mockito, Hibernate, Datadog agent.
Unsafe (sun.misc.Unsafe / jdk.internal.misc.Unsafe): off-heap memory, low-level CAS, raw object construction. Encapsulated since JDK 9; access requires --add-opens. Replacements:
VarHandlefor atomic ops.MemorySegment(Panama) for off-heap memory.Lookup.defineHiddenClassfor runtime class definition.
Java agents & instrumentation:
java.lang.instrument.Instrumentation— bytecode rewriting at load or runtime.- Premain agent:
-javaagent:agent.jar. - Dynamic attach:
VirtualMachine.attach(pid).loadAgent(...). - Used by: Datadog APM, New Relic, Mockito, IntelliJ debugger, JaCoCo coverage.
MethodHandle / invokedynamic: the bytecode behind lambdas, string concat (StringConcatFactory), and pattern matching dispatch. Build dynamic call sites with LambdaMetafactory.
GraalVM native-image: AOT compile JVM bytecode to a native binary. Closed-world assumption — reflection / dynamic class loading needs config. Used by Quarkus, Micronaut, Spring Native, Helidon, Picocli for fast-startup CLIs.
Project Leyden: AOT class loading + linking + (eventually) AOT-compiled native code without GraalVM’s closed-world restriction.
Project Valhalla: value classes + primitive classes + universal generics — flatten layout, eliminate boxing. In preview.
Project Loom: virtual threads (delivered) + structured concurrency + scoped values.
Project Panama: FFM API (delivered) + Vector API (incubator: SIMD intrinsics).
Custom build phases / annotation processors:
javax.annotation.processing— generate code at compile time. Used by Lombok (legacy hack via internal APIs), AutoValue, Dagger, Immutables, MapStruct.- Maven plugins:
maven-compiler-pluginconfigures processors. Gradle:annotationProcessorconfiguration.
Embedding the JVM: Invocation API (JNI_CreateJavaVM) or Panama-based approach. libjvm.so is the runtime; you load it from C/C++.
Class-File API (JEP 484, finalized JDK 24): read/write/transform .class files from the standard library — no more depending on ASM, ByteBuddy, BCEL for basic bytecode work. The JDK itself migrated internal users in 24+.
// Generate a class at runtime with the Class-File API
byte[] bytes = ClassFile.of().build(ClassDesc.of("Hello"), classBuilder -> {
classBuilder.withMethod("main", MethodTypeDesc.of(CD_void, CD_String.arrayType()),
ACC_PUBLIC | ACC_STATIC, mb -> mb.withCode(cb -> cb
.getstatic(CD_System, "out", CD_PrintStream)
.loadConstant("Hello, world!")
.invokevirtual(CD_PrintStream, "println", MethodTypeDesc.of(CD_void, CD_String))
.return_()));
});Project Loom continuations (low-level API, not yet public): the virtual-thread engine. Continuation.yield(scope) parks; resuming reschedules on a carrier. Will eventually expose user-defined coroutines.
Project Babylon (Code Reflection JEP draft): reflect over Java code itself — translate Java methods to other IRs (SQL, GPU kernels, ONNX). Enables Java GPU offload (TornadoVM-style) and ML compilers without bytecode hackery.
Idioms & style
- Naming:
PascalCasefor classes/interfaces,camelCasefor methods/vars,SCREAMING_SNAKEfor constants. Packageslowercase.dotted.like.com.example. - Formatters:
google-java-format(canonical), Spotless (build-tool wrapper), Eclipse formatter, IntelliJ built-in. - Linters: Checkstyle, SpotBugs (formerly FindBugs), Error Prone (Google, plugs into javac), PMD, SonarQube/SonarLint.
- Style guides: Oracle Code Conventions (1999, dated), Google Java Style (https://google.github.io/styleguide/javaguide.html), team-specific.
- Idiomatic patterns:
- Records for data carriers; sealed interfaces + records for sum types.
Optional<T>for “may return null” returns (NOT for fields/params).- Immutability where possible —
finalfields,List.copyOf(...). - Streams for collection pipelines, but loops are fine when clearer.
- Try-with-resources for any
AutoCloseable. - Builder pattern (
@Builderfrom Lombok or hand-rolled) for big constructors. - Dependency injection (Spring, Guice, Dagger, Quarkus CDI, Micronaut).
- Reviewer tells: mutable static state, swallowing exceptions, raw types (
Listinstead ofList<String>),nullreturns instead ofOptional/empty collection,equals/hashCodemismatch, missing@Override, abuse of inheritance over composition.
Ecosystem
| Domain | Tools |
|---|---|
| Web frameworks | Spring Boot 3.4+, Quarkus 3.15+, Micronaut 4.7+, Helidon 4+ (Nima — virtual-thread native), Vert.x 4.5+, Javalin, Play |
| Reactive | Project Reactor (Reactor Core 3.7+), RxJava 3, Akka (now BSL-licensed; Pekko is the Apache fork), Mutiny (Quarkus) |
| ORM / Persistence | Hibernate ORM 6.x / JPA 3.1, jOOQ, MyBatis, Spring Data, EclipseLink, Ebean |
| Testing | JUnit 5 (Jupiter), TestNG, Mockito, AssertJ, Hamcrest, Testcontainers (containers in tests, ubiquitous), ArchUnit (architecture tests), Pitest (mutation testing), WireMock |
| Build | Maven, Gradle 8.x (Kotlin DSL preferred), Bazel, Mill (Scala-DSL), Bld (Java-DSL) |
| Big Data / Stream | Apache Spark, Flink, Kafka, Beam, Hadoop, Cassandra, Elasticsearch / OpenSearch, Lucene, Pulsar |
| Web servers | Tomcat, Jetty, Undertow, Netty (async, NIO), Helidon Nima (virtual-thread native — sync API, async perf) |
| Microservice frameworks | Spring Cloud, Quarkus (live-reload, fast native), Micronaut (compile-time DI), Helidon |
| Observability | Micrometer, OpenTelemetry Java agent (auto-instrumentation for 100+ libs), JFR, Datadog APM, New Relic, Honeycomb beeline |
| Docs | Javadoc (built-in), AsciiDoctor (asciidoclet) |
| Native / cloud | GraalVM Native Image (21+: Profile-Guided Optimization, optimized G1), Spring Native (Spring Boot 3+), Quarkus, Buildpacks (Paketo), Jib (Docker images without Dockerfile), Liquibase, Flyway, Testcontainers Cloud |
| LLM / AI | LangChain4j, Spring AI (1.0 GA 2024), Quarkus LangChain4j, DJL (Deep Java Library), Tribuo (Oracle), Apache OpenNLP |
| IDEs | IntelliJ IDEA (Ultimate / Community), Eclipse, VS Code (with Java extension pack — Red Hat / Microsoft), NetBeans, JetBrains Fleet (multi-language IDE) |
| Notable users | every bank ever, Netflix, LinkedIn, Twitter (originally), Amazon, Google (Android — desugared from JDK 11+ via R8, internal services), Alibaba (Dragonwell JDK), Uber, Airbnb, Stripe, JetBrains (IntelliJ Platform) |
Gotchas
- Type erasure:
new T[10]doesn’t work; can’t overload onList<String>vsList<Integer>;instanceof List<String>is illegal. - Autoboxing:
Longvslongin==—==on boxed types compares references. Always use.equals()or unbox. Integercache:Integer.valueOf(127) == Integer.valueOf(127)is true,128 == 128is false.- Checked exceptions in lambdas:
Stream.map(x -> Files.readString(p))won’t compile — wrap inFunctionthat re-throws as unchecked. Dateis awful — mutable, broken timezone handling. Usejava.time(Instant,LocalDate,ZonedDateTime).equals/hashCodecontract — override both or neither. Records do it for you.HashMapthread safety — none. UseConcurrentHashMap.Collections.synchronizedList(...)doesn’t synchronize iteration — wrap insynchronized(list) { ... }block.- Static initialization order can deadlock between classes.
String.intern()can fill PermGen / metaspace.finalize()is deprecated; useCleaneror try-with-resources.- Modular access: since JDK 9,
setAccessible(true)on JDK internals requires--add-opens. Lombok and similar tools paper over this. Optionalis notSerializable; never use as a field type.- Newcomers from Python: verbose, no top-level functions (until JEP 512 in 25), checked exceptions feel painful, generics are erased so reflection-based libs need
TypeReference/TypeToken. - Newcomers from C#: Java has no properties (write
getX/setX), no operator overloading, noout/ref, no LINQ syntax (use Streams), generics are erased not reified.
Modernization timeline (8 → 25)
If you’re stuck on Java 8/11/17, the migration jumps to know:
| Version | Feature | Why it matters |
|---|---|---|
| 8 (2014) | Lambdas, streams, java.time, Optional | Modern Java starts here |
| 9 | Modules (JPMS), var keyword, JShell, G1 default | Module system mostly ignored outside JDK |
| 11 LTS | var in lambda params, HTTP client, String.repeat/strip/lines, single-file java Hello.java, ZGC + Epsilon experimental | First viable Oracle JDK alternative-distribution era |
| 14 | Switch expressions, NPE helpful messages | Cannot invoke "X.method()" because "y" is null |
| 15 | Text blocks ("""..."""), sealed (preview), hidden classes, Z+Shenandoah stable | Multi-line strings finally |
| 16 | Records, instanceof pattern, Stream toList(), Vector API incubator | Records are huge |
| 17 LTS | Sealed classes, RandomGenerator interface, deprecation of Security Manager | Long-supported safe stop |
| 19/20 | Virtual threads (preview), structured concurrency (incubator), pattern matching for switch (preview) | Loom’s first appearance |
| 21 LTS | Virtual threads GA, sequenced collections, pattern matching for switch + record patterns GA, generational ZGC | The “modern Java” baseline — adopt this if you can |
| 22 | FFM API GA, unnamed variables (_), stream gatherers (preview), region pinning ZGC | Panama is officially production-ready |
| 23 | ZGC generational-only, Module Import declarations (preview), markdown comments in Javadoc | Cleanup release |
| 24 | Class-File API GA, JEP 491 synchronize-without-pinning, stream gatherers GA, ahead-of-time class loading | Class-File API replaces ASM for stdlib uses |
| 25 LTS | Compact source files + instance main (JEP 512), scoped values GA, structured concurrency GA, key derivation API, PEM API | Beginner-friendly + Loom-complete |
Spring Boot 3+ requires Java 17, Quarkus 3+ requires 17, latest Hibernate 7 targets 17. Java 21 is the practical floor for new projects in 2026.
Records, sealed types, pattern matching — algebraic data types in Java
The 16/17/21 trio makes Java capable of expressing sum types ergonomically. Combined, they replace much of the Visitor pattern + class hierarchy boilerplate.
sealed interface Json permits JNull, JBool, JNum, JStr, JArr, JObj {}
record JNull() implements Json {}
record JBool(boolean v) implements Json {}
record JNum(double v) implements Json {}
record JStr(String v) implements Json {}
record JArr(List<Json> v) implements Json {}
record JObj(Map<String, Json> v) implements Json {}
String render(Json j) {
return switch (j) {
case JNull n -> "null";
case JBool(boolean b) -> Boolean.toString(b);
case JNum(double d) -> Double.toString(d);
case JStr(String s) -> "\"" + s + "\"";
case JArr(List<Json> xs) -> xs.stream().map(this::render).collect(joining(",", "[", "]"));
case JObj(Map<String, Json> m) -> m.entrySet().stream()
.map(e -> "\"" + e.getKey() + "\":" + render(e.getValue()))
.collect(joining(",", "{", "}"));
};
}Exhaustiveness is checked: omit JArr and you get a compile error. Record destructuring + guards (case JNum(var d) when d > 0) cover most pattern-match needs.
Build tools comparison
| Tool | Config | Speed | When |
|---|---|---|---|
| Maven | pom.xml (XML, declarative) | medium | Enterprise default, conservative, huge plugin ecosystem |
| Gradle 8.x | build.gradle.kts (Kotlin DSL preferred) | fast w/ daemon | Android default, monorepo-friendly, configuration caching |
| Bazel | BUILD.bazel (Starlark) | fastest w/ cache | Monorepo scale, hermetic, multi-language |
| Mill | build.sc (Scala DSL) | fast | Mixed Java/Scala projects, no daemon required |
| Bld | Java-based config | fast | Pure-Java projects, no DSL learning |
Gradle 8 highlights: configuration cache (incrementally serializes the dep graph), version catalogs (libs.versions.toml), composite builds, build scans. Maven 4 (released late 2024) adds CI-friendly versioning, --offline performance, build subscription model.
Spring Boot 3 / Quarkus / Micronaut quick comparison
| Framework | Startup | Memory | Native ready | DI model |
|---|---|---|---|---|
| Spring Boot 3.4+ | ~1-3s JVM, ~50ms native | 200-500 MB | Spring Native + GraalVM | reflection at runtime (slow start, fastest after warmup) |
| Quarkus 3.15+ | ~0.5s JVM, ~10ms native | 100-200 MB | first-class native | compile-time DI (Arc), live reload |
| Micronaut 4.7+ | ~0.5s JVM, ~10ms native | 80-150 MB | first-class native | compile-time DI (annotation processor) |
| Helidon 4 Nima | ~0.3s JVM | 60-120 MB | Yes | first-class virtual threads |
For new microservices in 2026, Quarkus and Micronaut dominate cloud-native (cheaper Lambda/Knative cold starts), Spring Boot still owns enterprise where breadth wins. Helidon Nima is the showcase for what virtual threads enable: synchronous code, async-like throughput.
Code examples — virtual threads + Loom (JDK 21+)
// Concurrent fetch with virtual threads + structured concurrency
import java.util.concurrent.StructuredTaskScope;
import java.net.http.*;
import java.net.URI;
record Page(String url, String body) {}
List<Page> fetchAll(List<String> urls) throws InterruptedException {
var client = HttpClient.newHttpClient();
try (var scope = StructuredTaskScope.<Page>open()) {
var futures = urls.stream()
.map(url -> scope.fork(() -> {
var req = HttpRequest.newBuilder(URI.create(url)).build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
return new Page(url, resp.body());
}))
.toList();
scope.join(); // wait for all (or fail-fast)
return futures.stream().map(Subtask::get).toList();
}
}
// Server with virtual threads: classic synchronous code, async-like throughput
try (var server = HttpServer.create(new InetSocketAddress(8080), 0)) {
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
server.createContext("/", exchange -> {
var body = "Hello, " + exchange.getRequestURI().getQuery();
exchange.sendResponseHeaders(200, body.length());
try (var os = exchange.getResponseBody()) { os.write(body.getBytes()); }
});
server.start();
}Foreign Function & Memory API (Panama, JDK 22+)
Replaces JNI for calling native libs. Type-safe, no header generation needed at runtime, supports off-heap memory with structured arenas.
import java.lang.foreign.*;
import static java.lang.foreign.ValueLayout.*;
// Call C strlen via FFM
var linker = Linker.nativeLinker();
var stdlib = SymbolLookup.loaderLookup();
var strlen = linker.downcallHandle(
stdlib.find("strlen").orElseThrow(),
FunctionDescriptor.of(JAVA_LONG, ADDRESS)
);
try (var arena = Arena.ofConfined()) {
MemorySegment cstr = arena.allocateUtf8String("Hello, FFM!");
long len = (long) strlen.invoke(cstr);
System.out.println("len = " + len); // 11
}jextract (tool) auto-generates Java bindings from C header files — drop a .h and get a Java API. Used by Apache Lucene’s 9.10+ Vector API integration, Neo4j, and any new native interop in 2024-26.
JVM tuning playbook (GC selection matrix + heap sizing)
Pick a GC by workload shape. JDK 25 ships six collectors; G1 is the default, ZGC is the modern low-pause winner.
| GC | Pause goal | Heap size | Throughput cost | Use case |
|---|---|---|---|---|
| Serial | seconds | <100 MB | none (single-thread) | Containers, CI runners, embedded |
| Parallel | seconds | 1-32 GB | best throughput | Batch jobs, Spark workers |
| G1 (default) | <200ms typical | 4 GB - 64 GB | ~5-10% vs Parallel | Most apps — balanced |
| Shenandoah | <10ms | 4 GB - 1 TB | ~10-15% | Red Hat / OpenJDK, alt low-pause |
| Generational ZGC | <1ms | 8 GB - multi-TB | ~5-10% | Latency-critical (trading, ads, real-time) |
| Epsilon | n/a (no-op) | any | none (no collection) | Benchmarking, short-lived jobs |
Heap sizing rules of thumb (production):
- Set
-Xms = -Xmxto avoid heap-grow stalls and reduce CDS-cache thrash. - Container memory limit: heap should be ~70-75% of container limit. Reserve ~25% for metaspace, code cache, direct buffers, thread stacks, GC overhead.
- For G1 in containers >4 GB:
-XX:MaxGCPauseMillis=100 -XX:+UseStringDeduplication. - For ZGC:
-XX:+UseZGC -Xmx<N>g, no other knobs needed; ZGC self-tunes. -XX:+AlwaysPreTouchin latency-sensitive prod — touches all heap pages at startup so faults don’t happen during requests.- Container awareness: JDK 17+ honors cgroup v2 limits; pre-17 needs
-XX:+UseContainerSupport.
Off-heap caching options when GC pressure is real:
- Chronicle Map — memory-mapped, persistent, multi-process; nanosecond reads.
- Caffeine — on-heap but spec-perfect (W-TinyLFU); replaces Guava Cache; ~3x faster.
- Hazelcast IMDG / Apache Ignite — distributed off-heap.
- EhCache 3 — tiered (heap → off-heap → disk).
- OpenHFT / Chronicle Queue — for journaling, IPC, low-latency message bus.
Native image (GraalVM + Project Leyden)
Two paths to AOT-compiled native Java in 2026:
GraalVM native-image — closed-world, fastest cold start (5-50ms), smallest binary (30-60 MB stripped).
# With Maven (native-maven-plugin):
mvn -Pnative native:compile
# Direct:
native-image -jar app.jar --no-fallback --enable-preview \
-H:Name=app -H:+ReportExceptionStackTraces \
--initialize-at-build-time=org.slf4jClosed-world means all classes must be reachable at build time; reflection, dynamic class loading, JNI need reachability-metadata.json (libraries ship these; Spring + Quarkus + Micronaut auto-generate). Tradeoff: no JIT means peak throughput is ~10-20% lower than HotSpot after warmup — but cold start is 100-1000x better.
Quarkus / Micronaut / Spring Boot AOT — first-class native:
- Quarkus:
./mvnw package -Dnative— Quarkus does build-time DI resolution, indexes all reflection, compiles to native via GraalVM. ~10ms cold start, ~30 MB resident. - Micronaut: compile-time DI via annotation processors → no runtime reflection needed → cleanest GraalVM story.
- Spring Boot 3.4+ Native with
spring-boot-starter-parent+<image><builder>paketobuildpacks/builder-jammy-tiny</builder></image>— Spring AOT runs before native-image, generatesBeanFactoryInitializerclasses.
Project Leyden (JEP 483, ramping through 25-28) — AOT class loading + linking + AOT method profiling cache without closed-world.
# Record a training run
java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -jar app.jar
# Build AOT cache
java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -jar app.jar
# Run with AOT cache (instant startup)
java -XX:AOTCache=app.aot -jar app.jarIn 25, the cache contains preloaded classes + linked methods + profile data. Future Leyden milestones (26-28) add AOT-compiled native code, eventually closing the gap with GraalVM without the closed-world constraint.
Records + pattern matching: end-to-end worked example
Modern Java replaces visitor-pattern + class hierarchies with sealed interfaces + records + switch pattern matching. A complete worked example modeling HTTP responses:
sealed interface HttpResult<T> permits Ok, NotFound, ServerError, Redirect {}
record Ok<T>(T body, int status) implements HttpResult<T> {}
record NotFound<T>(String resource) implements HttpResult<T> {}
record ServerError<T>(Throwable cause, String requestId) implements HttpResult<T> {}
record Redirect<T>(String location, boolean permanent) implements HttpResult<T> {}
// Exhaustive handler — compiler checks every variant
static <T> String render(HttpResult<T> r) {
return switch (r) {
case Ok<T>(var body, var status) when status < 300 ->
"OK %d: %s".formatted(status, body);
case Ok<T>(var body, var status) ->
"Non-2xx OK?? %d: %s".formatted(status, body);
case NotFound<T>(var resource) ->
"404: %s".formatted(resource);
case ServerError<T>(var cause, var rid) ->
"500 [%s]: %s".formatted(rid, cause.getMessage());
case Redirect<T>(var loc, true) ->
"301 -> %s".formatted(loc);
case Redirect<T>(var loc, false) ->
"302 -> %s".formatted(loc);
};
}Add a new variant to the sealed interface? Every switch over it becomes a compile error until you handle the new case. This is Java’s path to ADT-style ergonomics without leaving the existing type system.
Modern HTTP client (java.net.http) — HTTP/2 + WebSocket + SSE
The HttpClient in java.net.http (JDK 11+, fully fledged by 21+) replaces Apache HttpClient for most uses.
import java.net.URI;
import java.net.http.*;
import java.net.http.HttpClient.Version;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Duration;
var client = HttpClient.newBuilder()
.version(Version.HTTP_2) // auto-negotiate, fallback to 1.1
.connectTimeout(Duration.ofSeconds(5))
.followRedirects(HttpClient.Redirect.NORMAL)
.executor(Executors.newVirtualThreadPerTaskExecutor()) // 21+: virtual threads
.build();
// Sync HTTP/2 GET
var req = HttpRequest.newBuilder(URI.create("https://api.example.com/users/1"))
.header("Accept", "application/json")
.timeout(Duration.ofSeconds(10))
.GET()
.build();
var resp = client.send(req, BodyHandlers.ofString());
System.out.println(resp.statusCode() + " " + resp.body());
// Async with CompletableFuture
client.sendAsync(req, BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
// WebSocket
var ws = client.newWebSocketBuilder()
.buildAsync(URI.create("wss://api.example.com/stream"), new WebSocket.Listener() {
@Override public CompletionStage<?> onText(WebSocket ws, CharSequence data, boolean last) {
System.out.println("got: " + data);
return null;
}
}).join();
ws.sendText("hello", true);
// Server-Sent Events via BodyHandlers.fromLineSubscriber
var sseReq = HttpRequest.newBuilder(URI.create("https://api.example.com/events"))
.header("Accept", "text/event-stream").build();
client.send(sseReq, BodyHandlers.fromLineSubscriber(new java.util.concurrent.Flow.Subscriber<String>() {
public void onSubscribe(Flow.Subscription s) { s.request(Long.MAX_VALUE); }
public void onNext(String line) { System.out.println(line); }
public void onError(Throwable t) { }
public void onComplete() { }
}));Combined with virtual threads, the synchronous client.send() API gives async-equivalent throughput — millions of concurrent in-flight requests on a 64-core box, no callbacks needed.
Citations
- Oracle Java SE docs (25): https://docs.oracle.com/en/java/javase/25/
- Java Language Specification: https://docs.oracle.com/javase/specs/jls/se25/html/index.html
- Java Virtual Machine Specification: https://docs.oracle.com/javase/specs/jvms/se25/html/index.html
- OpenJDK: https://openjdk.org/
- JEP index: https://openjdk.org/jeps/0
- JEP 444 (virtual threads): https://openjdk.org/jeps/444
- JEP 480 (structured concurrency): https://openjdk.org/jeps/480
- JEP 512 (compact source files / instance main): https://openjdk.org/jeps/512
- Foreign Function & Memory API (JEP 454): https://openjdk.org/jeps/454
- Google Java Style: https://google.github.io/styleguide/javaguide.html
- GraalVM Native Image: https://www.graalvm.org/latest/reference-manual/native-image/
- ByteBuddy: https://bytebuddy.net/
- Adoptium / Temurin: https://adoptium.net/
- Wikipedia (version history reference): https://en.wikipedia.org/wiki/Java_version_history