This article covers the most important additions introduced in the five Long-Term Support (LTS) releases: Java 8, Java 11, Java 17, Java 21, and Java 25.
Java 8 (LTS)
Java 8 was one of the biggest releases in Java's history.It introduced functional programming, the Streams API, CompletableFuture, and the modern Date & Time API, fundamentally changing how Java applications are written today.
Lambda Expressions
Lambda expressions provide a concise way to represent anonymous functions, making the code more readable and reducing boilerplate.Before Java 8
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
Java 8
Collections.sort(names, (a, b) -> a.compareTo(b));
Lambdas are widely used with Streams, Collections, and functional interfaces.
Functional Interfaces
A functional interface contains exactly one abstract method and serves as the target for lambda expressions.@FunctionalInterface
interface Calculator {
int add(int a, int b);
}
Calculator calculator = (a, b) -> a + b;
System.out.println(calculator.add(10, 20));
Common built-in functional interfaces include:
- Predicate
- Function
- Consumer
- Supplier
- UnaryOperator
- BinaryOperator
Method References
Method references provide a shorter syntax for calling existing methods.Before
names.forEach(name -> System.out.println(name));
After
names.forEach(System.out::println);
Types of method references:
- Class::staticMethod
- object::instanceMethod
- Class::instanceMethod
- Class::new
Streams API
The Streams API enables functional-style operations on collections without modifying the original data.Before Java 8
List<String> result = new ArrayList<>();
for (String name : names) {
if (name.startsWith("A")) {
result.add(name.toUpperCase());
}
}
Java 8
List<String> result =
names.stream()
.filter(name -> name.startsWith("A"))
.map(String::toUpperCase)
.toList();
Common Stream operations:
- filter()
- map()
- flatMap()
- sorted()
- distinct()
- limit()
- skip()
- peek()
- reduce()
- collect()
Collectors
Collectors transform stream results into different data structures or perform aggregation.List<String> list =
names.stream().collect(Collectors.toList());
Map<String, Integer> map =
students.stream().collect(Collectors.toMap(tudent::getName, Student::getAge));
Useful collectors:
- toList()
- toSet()
- toMap()
- groupingBy()
- partitioningBy()
- joining()
- counting()
- mapping()
Optional
Optional helps avoid NullPointerException by explicitly representing the presence or absence of a value.Before Java 8
if (user != null) {
System.out.println(user.getName());
}
Java 8
Optional.ofNullable(user)
.map(User::getName)
.ifPresent(System.out::println);
If the input is non-null, it returns an Optional containing the value; if the input is null, it gracefully returns an empty Optional (acting like Optional.empty()) instead of throwing a NullPointerException.
Common methods:
- of()
- ofNullable()
- empty()
- isPresent()
- ifPresent()
- orElse()
- orElseGet()
- orElseThrow()
- map()
- filter()
Tip: Avoid using Optional as an entity field, method parameter, or collection element. It is primarily intended for return types.
Default Methods
Interfaces can now provide default implementations without breaking existing implementations.interface Vehicle {
default void start() {
System.out.println("Starting...");
}
}
This allows new methods to be added to interfaces while maintaining backward compatibility.
Static Methods in Interfaces
Interfaces can also define static utility methods.interface MathUtil {
static int square(int n) {
return n * n;
}
}
System.out.println(MathUtil.square(5));
CompletableFuture
CompletableFuture simplifies asynchronous programming and supports chaining multiple asynchronous tasks.CompletableFuture
.supplyAsync(() -> "Hello")
.thenApply(String::toUpperCase)
.thenAccept(System.out::println);
Common methods:
- supplyAsync()
- runAsync()
- thenApply()
- thenAccept()
- thenCompose()
- thenCombine()
- exceptionally()
- join()
- allOf()
- anyOf()
Date & Time API
Java 8 introduced the java.time package, replacing the old Date and Calendar APIs.Before Java 8
Date date = new Date();
Java 8
LocalDate today = LocalDate.now();
LocalDateTime now = LocalDateTime.now();
LocalTime time = LocalTime.now();
Common classes:
- LocalDate
- LocalTime
- LocalDateTime
- Instant
- Duration
- Period
- ZoneId
- DateTimeFormatter
Base64 API
Java 8 added built-in support for Base64 encoding and decoding.String encoded = Base64.getEncoder()
.encodeToString("Hello".getBytes());
String decoded = new String(Base64.getDecoder()
.decode(encoded));
Repeatable Annotations
The same annotation can now be applied multiple times.@Role("ADMIN")
@Role("USER")
public class Employee {
}
Type Annotations
Annotations can now be applied to any use of a type.List<@NonNull String> names;
This feature is mainly used by static analysis and validation tools.
Java 11 (LTS)
Java 11 is the second Long-Term Support (LTS) release and focuses on improving developer productivity, standardizing APIs, and enhancing JVM performance.It introduced the modern HTTP Client, several useful String and Files APIs, and important JVM improvements like ZGC and Java Flight Recorder.
HTTP Client API
Java 11 introduced a modern HTTP client that replaces the olderHttpURLConnection API. It supports both synchronous and asynchronous HTTP requests and provides a much cleaner API.
Before Java 11
URL url = new URL("https://example.com");
HttpURLConnection connection =
(HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
Java 11
HttpClient client = HttpClient.newHttpClient();
HttpRequest request =
HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.build();
HttpResponse<String> response =
client.send(
request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
String API Enhancements
Java 11 added several useful methods to theString class.
String text = " Hello Java ";
text.isBlank();
text.strip();
text.stripLeading();
text.stripTrailing();
text.repeat(3);
text.lines();
Files API Enhancements
Reading and writing files became much simpler.Before Java 11
List<String> lines = Files.readAllLines(path);
Java 11
String text = Files.readString(path);
Files.writeString(
path,
"Hello Java");
Collection.toArray()
Collections can now convert directly using constructor references.Before Java 11
String[] array = list.toArray(new String[0]);
Java 11
String[] array = list.toArray(String[]::new);
Optional Enhancements
Several new methods were added to makeOptional easier to use.
Optional<String> value = Optional.of("Java");
value.ifPresentOrElse(
System.out::println,
() -> System.out.println("Empty"));
Useful additions:
- isEmpty()
- ifPresentOrElse()
- or()
Running Java Without Compilation
Small programs can now be executed directly without explicitly compiling them.Before Java 11
javac Hello.java
java Hello
Java 11
java Hello.java
This is especially useful for scripts, demos, and quick experiments.
Z Garbage Collector (ZGC)
Java 11 introduced ZGC, a low-latency garbage collector designed for applications with very large heaps.Key benefits:
- Low pause times (typically less than 10 ms)
- Handles multi-terabyte heaps
- Suitable for large enterprise systems
Java Flight Recorder (JFR)
Java Flight Recorder became available without requiring a commercial license. It helps diagnose:- Memory issues
- CPU bottlenecks
- Thread contention
- Garbage Collection behavior
- Performance problems
JFR is widely used for production monitoring and performance tuning.
Removed Java EE Modules
Several Java EE and CORBA modules were removed from the JDK.Examples include:
- JAXB
- JAX-WS
- CORBA
- Activation
Applications depending on these modules must now include them as external dependencies.
Java 17 (LTS)
Java 17 is the third Long-Term Support (LTS) release and introduced several language enhancements that improve readability, reduce boilerplate code, and make object-oriented programming more expressive.Key additions include Records, Sealed Classes, Pattern Matching, and Text Blocks.
Records
Records provide a concise way to create immutable data classes. The compiler automatically generates constructors, getters,equals(), hashCode(), and toString().
Before Java 17
public class User {
private final String name;
private final int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public boolean equals(Object o) {
// omitted
}
@Override
public int hashCode() {
// omitted
}
@Override
public String toString() {
// omitted
}
}
Java 17
public record User(
String name,
int age) {
}
Records are ideal for DTOs, API requests/responses, configuration objects, and immutable models.
Sealed Classes
Sealed classes restrict which classes can extend or implement them, giving developers more control over inheritance.Before Java 17
class Shape {
}
Any class could extend Shape.
Java 17
public sealed class Shape permits Circle, Rectangle {
}
public final class Circle
extends Shape {
}
public final class Rectangle
extends Shape {
}
Every permitted subclass must explicitly state how it continues the boundary using exactly one of three modifiers:
1. final: The subclass is fully closed and cannot be extended further.
public final class Car extends Vehicle {
}
2. sealed: The subclass is also sealed but defines its own limited subset of children.
public sealed class Truck
extends Vehicle
permits SemiTruck, PickupTruck {
}
public final class SemiTruck
extends Truck {
}
public final class PickupTruck
extends Truck {
}
3. non-sealed: The subclass breaks the sealing chain and re-opens itself to traditional open inheritance.
public non-sealed class Bicycle extends Vehicle {
}
Pattern Matching for instanceof
Pattern matching removes the need for explicit casting after an instanceof check.Before Java 17
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str.length());
}
Java 17
if (obj instanceof String str) {
System.out.println(str.length());
}
The variable str is automatically cast if the condition is true.
Text Blocks
Text Blocks simplify writing multi-line strings without excessive concatenation or escape characters.Before Java 17
String json =
"{\n" +
" \"name\": \"John\",\n" +
" \"age\": 30\n" +
"}";
Java 17
String json = """
{
"name": "John",
"age": 30
}
""";
Commonly used for:
- SQL Queries
- JSON
- XML
- HTML
- Multi-line messages
Helpful NullPointerException
Java 17 provides more descriptive NullPointerException messages, making debugging much easier.Before
NullPointerException
Java 17
Cannot invoke "Address.getCity()" because "user.getAddress()" is null
No code changes are required; the JVM generates the detailed message automatically.
RandomGenerator API
Java 17 introduced the RandomGenerator interface, providing a common API for different random number generators.RandomGenerator random = RandomGenerator.getDefault();
System.out.println(random.nextInt(100));
It offers better flexibility than directly using Random.
Strong Encapsulation
Java 17 strongly encapsulates JDK internals, preventing applications from accessing internal APIs unless explicitly allowed.This improves:
- Security
- Maintainability
- Future compatibility
Applications relying on internal JDK classes should migrate to supported public APIs.
Java 21 (LTS)
Java 21 is the fourth Long-Term Support (LTS) release and introduces significant improvements in concurrency, pattern matching, and collection APIs.The standout feature is Virtual Threads, which enables applications to handle millions of concurrent tasks with minimal resource usage.
Virtual Threads
Virtual Threads are lightweight threads managed by the JVM instead of the operating system. They make it possible to create millions of concurrent tasks without the overhead of platform threads.Before Java 21
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> {
System.out.println("Task");
});
executor.shutdown();
Java 21
try (ExecutorService executor =
Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
System.out.println("Task");
});
}
Virtual Threads are ideal for I/O-bound applications such as web servers, REST APIs, database access, and microservices.
Tip: Virtual Threads improve scalability for blocking I/O but do not make CPU-intensive tasks faster.
Pattern Matching for switch
Switch statements can now match object types directly, making complex conditional logic cleaner and easier to read.Before Java 21
if (obj instanceof String) {
System.out.println(((String) obj).toUpperCase());
} else if (obj instanceof Integer) {
System.out.println((Integer) obj * 2);
}
Java 21
switch (obj) {
case String s ->
System.out.println(s.toUpperCase());
case Integer i ->
System.out.println(i * 2);
default ->
System.out.println("Unknown");
}
Record Patterns
Record Patterns allow records to be deconstructed directly into their components.public record User(
String name,
int age) {
}
Object obj = new User("John", 30);
if (obj instanceof User(String name, int age)) {
System.out.println(name);
System.out.println(age);
}
This removes the need to call individual accessor methods.
Sequenced Collections
Java 21 introduced the SequencedCollection, SequencedSet, and SequencedMap interfaces, providing a consistent way to access the first and last elements of ordered collections.List<String> names =
new ArrayList<>(List.of("A", "B", "C"));
System.out.println(names.getFirst());
System.out.println(names.getLast());
Useful methods include:
- getFirst()
- getLast()
- removeFirst()
- removeLast()
- reversed()
Java 25 (LTS)
Java 25 is the latest Long-Term Support (LTS) release, focusing on performance, stability, security, and developer productivity.Unlike Java 8 or Java 21, it does not introduce many revolutionary language features.
Instead, it builds upon recent releases by finalizing preview features, improving the JVM, enhancing garbage collection, and delivering better overall performance for modern enterprise applications.
Primitive Types in Pattern Matching
Pattern matching has been extended to support primitive types, making switch statements more expressive.Before Java 25
int number = 10;
switch (number) {
case 1:
System.out.println("One");
break;
default:
System.out.println("Other");
}
Java 25
switch (number) {
case int i when i > 0 ->
System.out.println("Positive");
case int i when i < 0 ->
System.out.println("Negative");
default ->
System.out.println("Zero");
}
Scoped Values (Final)
Scoped Values provide a safer and more efficient alternative to ThreadLocal, especially when working with Virtual Threads.static final ScopedValue<String> USER = ScopedValue.newInstance();
ScopedValue.runWhere(
USER,
"John",
() -> System.out.println(USER.get()));
Unlike ThreadLocal, Scoped Values are immutable and automatically cleaned up after execution.
Structured Concurrency
Structured Concurrency simplifies coordinating multiple concurrent tasks by treating them as a single unit of work.try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
var user = scope.fork(() -> loadUser());
var orders = scope.fork(() -> loadOrders());
scope.join();
System.out.println(user.resultNow());
}
This improves cancellation, exception handling, and resource management.
Java LTS Evolution Summary
| Java Version | Major Features |
|---|---|
| Java 8 | Lambda Expressions, Streams API, Functional Interfaces, Method References, Optional, CompletableFuture, Date & Time API |
| Java 11 | HTTP Client API, String APIs, Files API, Optional Enhancements, Z Garbage Collector (ZGC), Java Flight Recorder (JFR) |
| Java 17 | Records, Sealed Classes, Pattern Matching for instanceof, Text Blocks, Helpful NullPointerExceptions |
| Java 21 | Virtual Threads, Record Patterns, Pattern Matching for switch, Sequenced Collections |
| Java 25 | Scoped Values, Structured Concurrency, Primitive Pattern Matching, JVM & Garbage Collection Improvements |
Conclusion
Modern Java has evolved far beyond the Java 8 era, introducing features that make applications more concise, expressive, scalable, and performant.Whether you're writing cleaner code with Records, simplifying asynchronous programming with Virtual Threads, or leveraging Pattern Matching to reduce boilerplate, these LTS releases provide significant improvements for everyday development.