Skip to main content
Java · Topic 10 of 18

Java 8 to 25: What Matters

Definition​

Modern Java means the features added since Java 8: lambdas and streams, var, records, sealed types, pattern matching, text blocks and virtual threads. Teams usually run a long-term support release such as 17, 21 or 25.

Why it exists​

Java was known for boilerplate and heavy threads. Each release removed a pain point: less code for data classes, safer type checks, and cheap threads for blocking work.

How it works​

Timeline (LTS in bold)​

ReleaseHeadline features
8 (2014)Lambdas, streams, Optional, default methods, java.time, CompletableFuture
9Modules (JPMS), List.of/Set.of/Map.of, private interface methods, Flow API
10var for locals
11 (2018)HttpClient, String helpers (isBlank, strip, lines, repeat), var in lambdas, single-file launch
12-13Switch expressions (preview), text blocks (preview), Collectors.teeing
14Switch expressions final, helpful NullPointerException messages
15Text blocks final, ZGC production, hidden classes
16Records final, pattern matching for instanceof final, Stream.toList(), strong encapsulation of JDK internals begins
17 (2021)Sealed classes final, strong encapsulation default, new RandomGenerator API
18UTF-8 by default, simple web server jwebserver
19-20Virtual threads and structured concurrency (preview), record patterns (preview)
21 (2023)Virtual threads, pattern matching for switch and record patterns final, sequenced collections, generational ZGC
22-24Unnamed variables/patterns _ final (22), foreign function & memory API final (22), stream gatherers final (24), class-file API, ahead-of-time class loading (24)
25 (2025)Scoped values, module import declarations, compact source files and instance main, flexible constructor bodies, compact object headers, generational Shenandoah (all final in 25)

API and platform features​

  • java.time (8): immutable, thread-safe date/time; replace Date/Calendar and SimpleDateFormat (not thread-safe).
  • HttpClient (11): HTTP/2, async, CompletableFuture-based.
  • Sequenced collections (21): getFirst(), getLast(), reversed() across lists, deques, linked sets/maps and sorted sets.
  • Virtual threads (21): see Virtual threads.
  • Foreign Function & Memory API (22): safe, fast native interop replacing JNI and sun.misc.Unsafe use cases.
  • Vector API: still incubating; SIMD in Java.
  • Helpful NPE messages (14/15): the message names the null variable or call chain.
  • Stream.toList(), Collectors.teeing, String.formatted, Objects.requireNonNullElse, Optional.orElseThrow(), Map.entry.

JVM and runtime changes​

AreaChange
GCG1 default since 9; ZGC production since 15, generational ZGC in 21 (default mode in 23+); Shenandoah in OpenJDK builds; Parallel GC still available
Class sharingAppCDS and dynamic archives reduce startup (Spring Boot 3.3+ supports CDS; GraalVM native image is another route)
ContainersThe JVM is container-aware (cgroup limits): sizes heap from container memory (-XX:MaxRAMPercentage)
Strong encapsulation--illegal-access removed; reflective access to JDK internals needs --add-opens
Removed/deprecatedApplets, Nashorn, Security Manager (deprecated for removal), CMS, finalize (deprecated for removal)

Example code​

Language features worth adopting​

var (10)​

Local variable type inference. Use when the type is obvious from the right-hand side; avoid when it hides meaning.

var orders = orderRepository.findByStatus("NEW"); // ok
var x = compute(); // unclear: avoid

Switch expressions (14)​

int days = switch (month) {
case FEB -> 28;
case APR, JUN, SEP, NOV -> 30;
default -> 31;
};

No fall-through, exhaustive for enums/sealed types, and usable as an expression. yield returns a value from a block.

Text blocks (15)​

String json = """
{
"name": "%s",
"active": true
}
""".formatted(name);

Incidental indentation is stripped by the position of the closing delimiter.

Records (16)​

Transparent immutable data carriers: the compiler generates the constructor, accessors, equals, hashCode, toString.

public record Money(BigDecimal amount, String currency) {
public Money { // compact constructor: validation
Objects.requireNonNull(amount);
if (amount.signum() < 0) throw new IllegalArgumentException("negative");
}
public Money plus(Money other) { return new Money(amount.add(other.amount), currency); }
}

Limits: records are shallowly immutable (a List component can still be mutated; copy defensively with List.copyOf), cannot extend classes, and are poor JPA entities (no no-arg constructor, final fields). Excellent for DTOs, value objects, events, map keys, @ConfigurationProperties.

Sealed classes and interfaces (17)​

Restrict which types may extend or implement a type, enabling exhaustive pattern matching (see OOP and design).

Pattern matching (16 → 21)​

if (obj instanceof String s && s.length() > 3) { // type test + binding (16)
use(s);
}

String describe(Object o) {
return switch (o) { // pattern switch (21)
case null -> "null";
case Integer i when i > 100 -> "big int " + i; // guarded pattern
case Integer i -> "int " + i;
case String s -> "string " + s;
case Point(int x, int y) -> "point " + x + "," + y; // record pattern, deconstruction
default -> "other";
};
}

Unnamed variables (22)​

_ for values you must declare but do not use: catch (IOException _), case Circle _ ->, (a, _) -> a.

Java 25 additions​

  • Module import declarations: import module java.base; brings in all exported packages of a module.
  • Compact source files and instance main: a source file can contain just void main() { ... }, lowering the barrier for scripts and teaching.
  • Flexible constructor bodies: statements (validation, argument preparation) may appear before super(...)/this(...) as long as they do not use the instance under construction.
  • Scoped values: immutable, inheritable-by-child-threads context, a better alternative to ThreadLocal for virtual threads.
  • Structured concurrency remained in preview as of Java 25; check your JDK.

System design considerations​

  • Pick one LTS baseline per platform and upgrade on a schedule, not in a panic
  • Test with the target JDK in CI before production
  • Review JVM flags and GC defaults when upgrading, since they change between versions

Migration notes (8 → 17 → 21)​

  • Jakarta EE namespace switch (javax.* → jakarta.*) arrives with Spring Boot 3 / Spring Framework 6, which require Java 17+.
  • Libraries using deep reflection (older mocking, serialization, bytecode libraries) may need updates or --add-opens.
  • Test on the target JDK in CI; use jdeps to find internal API use and jdeprscan for deprecated APIs.
  • Default charset is UTF-8 from Java 18, which can change behaviour of code relying on the platform default.

When to use it, and when not​

Use it when

  • You start or upgrade a service and can target a recent LTS release
  • You model data with records and sealed types
  • You want virtual threads for blocking I/O workloads

Think twice when

  • A library or platform pins you to an old JDK
  • A feature is still in preview and the code must stay stable
  • The migration cost outweighs the benefit for a legacy system

Common mistakes​

  • Using var where it obscures types.
  • Treating record components as deeply immutable.
  • Records as JPA entities.
  • Enabling preview features in production builds.
  • Migrating the JDK without updating libraries (Lombok, ByteBuddy, Mockito, ASM, Hibernate).

Interview questions​

1. What are the main features introduced after Java 8 that you actually use?

Strong answer: records for DTOs/value objects, switch expressions, text blocks, var, sealed types plus pattern matching for domain results, HttpClient, Stream.toList(), virtual threads for I/O-heavy services, and sequenced collections; mention that you choose them for clarity and safety, not novelty.

2. Records vs Lombok @Value?

Records are language-level, standard, support pattern matching and deconstruction, and need no annotation processor; Lombok offers more customisation (builders, inheritance) but is a build-time dependency.

3. What is a sealed class for?

To restrict the permitted subtypes so the compiler can verify exhaustive switches and the domain model is closed and explicit.

4. What are virtual threads at a glance?

JVM-managed lightweight threads that make blocking-style code scale to hundreds of thousands of concurrent tasks (details in the concurrency section).

5. Which GC would you choose on Java 21?

G1 as the safe default; generational ZGC for very low pause requirements and large heaps; Parallel GC for batch throughput. Decide with measurements (see GC).

6. What changed in Java 17 that affects frameworks?

Strong encapsulation of JDK internals by default; --add-opens is needed for libraries doing deep reflection. Spring Boot 3 requires Java 17+.

7. What is the preview feature process?

Features ship as preview (need --enable-preview) for one or more releases to gather feedback and are then finalised, changed or dropped. Do not depend on previews in production libraries.

Check yourself​

Quick quiz3 questions

1What are records mainly for?

2Which of these is a Java 21 feature?

3Why do many teams jump from one LTS release to the next?

Key takeaways​

  • Know the LTS line: 8, 11, 17, 21, 25.
  • Adopt records, sealed types, pattern matching, switch expressions, text blocks, virtual threads.
  • Java 17+ is the baseline for modern Spring.
  • Verify what is preview versus final for your JDK.
Next topicThreads fundamentals

Finished this page? Track your progress (saved only in your browser).