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 GraalVM native-image and 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.class directly — pass Class<T> token).
  • Bounded type params: <T extends Comparable<T>>.
  • Records (JDK 16): public record Point(int x, int y) {} — immutable data carriers with auto equals/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-close

Concurrency 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): StructuredTaskScope for 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 most Unsafe atomic 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 inside synchronized (fixed in JDK 24, JEP 491) or native frames — prefer ReentrantLock in 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 on try-with-resources cancels 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 ThreadLocal is 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. jextract generates 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+ via RecordingStream. 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=record then =create then =on workflow 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    # disassemble

Bytecode 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:

  • VarHandle for atomic ops.
  • MemorySegment (Panama) for off-heap memory.
  • Lookup.defineHiddenClass for 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-plugin configures processors. Gradle: annotationProcessor configuration.

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: PascalCase for classes/interfaces, camelCase for methods/vars, SCREAMING_SNAKE for constants. Packages lowercase.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 — final fields, List.copyOf(...).
    • Streams for collection pipelines, but loops are fine when clearer.
    • Try-with-resources for any AutoCloseable.
    • Builder pattern (@Builder from Lombok or hand-rolled) for big constructors.
    • Dependency injection (Spring, Guice, Dagger, Quarkus CDI, Micronaut).
  • Reviewer tells: mutable static state, swallowing exceptions, raw types (List instead of List<String>), null returns instead of Optional/empty collection, equals/hashCode mismatch, missing @Override, abuse of inheritance over composition.

Ecosystem

DomainTools
Web frameworksSpring Boot 3.4+, Quarkus 3.15+, Micronaut 4.7+, Helidon 4+ (Nima — virtual-thread native), Vert.x 4.5+, Javalin, Play
ReactiveProject Reactor (Reactor Core 3.7+), RxJava 3, Akka (now BSL-licensed; Pekko is the Apache fork), Mutiny (Quarkus)
ORM / PersistenceHibernate ORM 6.x / JPA 3.1, jOOQ, MyBatis, Spring Data, EclipseLink, Ebean
TestingJUnit 5 (Jupiter), TestNG, Mockito, AssertJ, Hamcrest, Testcontainers (containers in tests, ubiquitous), ArchUnit (architecture tests), Pitest (mutation testing), WireMock
BuildMaven, Gradle 8.x (Kotlin DSL preferred), Bazel, Mill (Scala-DSL), Bld (Java-DSL)
Big Data / StreamApache Spark, Flink, Kafka, Beam, Hadoop, Cassandra, Elasticsearch / OpenSearch, Lucene, Pulsar
Web serversTomcat, Jetty, Undertow, Netty (async, NIO), Helidon Nima (virtual-thread native — sync API, async perf)
Microservice frameworksSpring Cloud, Quarkus (live-reload, fast native), Micronaut (compile-time DI), Helidon
ObservabilityMicrometer, OpenTelemetry Java agent (auto-instrumentation for 100+ libs), JFR, Datadog APM, New Relic, Honeycomb beeline
DocsJavadoc (built-in), AsciiDoctor (asciidoclet)
Native / cloudGraalVM 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 / AILangChain4j, Spring AI (1.0 GA 2024), Quarkus LangChain4j, DJL (Deep Java Library), Tribuo (Oracle), Apache OpenNLP
IDEsIntelliJ IDEA (Ultimate / Community), Eclipse, VS Code (with Java extension pack — Red Hat / Microsoft), NetBeans, JetBrains Fleet (multi-language IDE)
Notable usersevery 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 on List<String> vs List<Integer>; instanceof List<String> is illegal.
  • Autoboxing: Long vs long in ==== on boxed types compares references. Always use .equals() or unbox.
  • Integer cache: Integer.valueOf(127) == Integer.valueOf(127) is true, 128 == 128 is false.
  • Checked exceptions in lambdas: Stream.map(x -> Files.readString(p)) won’t compile — wrap in Function that re-throws as unchecked.
  • Date is awful — mutable, broken timezone handling. Use java.time (Instant, LocalDate, ZonedDateTime).
  • equals / hashCode contract — override both or neither. Records do it for you.
  • HashMap thread safety — none. Use ConcurrentHashMap.
  • Collections.synchronizedList(...) doesn’t synchronize iteration — wrap in synchronized(list) { ... } block.
  • Static initialization order can deadlock between classes.
  • String.intern() can fill PermGen / metaspace.
  • finalize() is deprecated; use Cleaner or try-with-resources.
  • Modular access: since JDK 9, setAccessible(true) on JDK internals requires --add-opens. Lombok and similar tools paper over this.
  • Optional is not Serializable; 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, no out/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:

VersionFeatureWhy it matters
8 (2014)Lambdas, streams, java.time, OptionalModern Java starts here
9Modules (JPMS), var keyword, JShell, G1 defaultModule system mostly ignored outside JDK
11 LTSvar in lambda params, HTTP client, String.repeat/strip/lines, single-file java Hello.java, ZGC + Epsilon experimentalFirst viable Oracle JDK alternative-distribution era
14Switch expressions, NPE helpful messagesCannot invoke "X.method()" because "y" is null
15Text blocks ("""..."""), sealed (preview), hidden classes, Z+Shenandoah stableMulti-line strings finally
16Records, instanceof pattern, Stream toList(), Vector API incubatorRecords are huge
17 LTSSealed classes, RandomGenerator interface, deprecation of Security ManagerLong-supported safe stop
19/20Virtual threads (preview), structured concurrency (incubator), pattern matching for switch (preview)Loom’s first appearance
21 LTSVirtual threads GA, sequenced collections, pattern matching for switch + record patterns GA, generational ZGCThe “modern Java” baseline — adopt this if you can
22FFM API GA, unnamed variables (_), stream gatherers (preview), region pinning ZGCPanama is officially production-ready
23ZGC generational-only, Module Import declarations (preview), markdown comments in JavadocCleanup release
24Class-File API GA, JEP 491 synchronize-without-pinning, stream gatherers GA, ahead-of-time class loadingClass-File API replaces ASM for stdlib uses
25 LTSCompact source files + instance main (JEP 512), scoped values GA, structured concurrency GA, key derivation API, PEM APIBeginner-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

ToolConfigSpeedWhen
Mavenpom.xml (XML, declarative)mediumEnterprise default, conservative, huge plugin ecosystem
Gradle 8.xbuild.gradle.kts (Kotlin DSL preferred)fast w/ daemonAndroid default, monorepo-friendly, configuration caching
BazelBUILD.bazel (Starlark)fastest w/ cacheMonorepo scale, hermetic, multi-language
Millbuild.sc (Scala DSL)fastMixed Java/Scala projects, no daemon required
BldJava-based configfastPure-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

FrameworkStartupMemoryNative readyDI model
Spring Boot 3.4+~1-3s JVM, ~50ms native200-500 MBSpring Native + GraalVMreflection at runtime (slow start, fastest after warmup)
Quarkus 3.15+~0.5s JVM, ~10ms native100-200 MBfirst-class nativecompile-time DI (Arc), live reload
Micronaut 4.7+~0.5s JVM, ~10ms native80-150 MBfirst-class nativecompile-time DI (annotation processor)
Helidon 4 Nima~0.3s JVM60-120 MBYesfirst-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.

GCPause goalHeap sizeThroughput costUse case
Serialseconds<100 MBnone (single-thread)Containers, CI runners, embedded
Parallelseconds1-32 GBbest throughputBatch jobs, Spark workers
G1 (default)<200ms typical4 GB - 64 GB~5-10% vs ParallelMost apps — balanced
Shenandoah<10ms4 GB - 1 TB~10-15%Red Hat / OpenJDK, alt low-pause
Generational ZGC<1ms8 GB - multi-TB~5-10%Latency-critical (trading, ads, real-time)
Epsilonn/a (no-op)anynone (no collection)Benchmarking, short-lived jobs

Heap sizing rules of thumb (production):

  • Set -Xms = -Xmx to 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:+AlwaysPreTouch in 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.slf4j

Closed-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, generates BeanFactoryInitializer classes.

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.jar

In 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