The Evolution of Java: Key Changes from Java 8 to Java 21

Thushan Jayasundara
7 min readSep 7, 2024

--

Java has been at the forefront of enterprise development for decades, evolving with each release to meet the demands of modern software engineering. From the introduction of lambdas in Java 8 to the revolutionary virtual threads in Java 21, the platform has consistently grown to provide developers with more powerful tools, simplified syntax, and improved performance.

In this article, we’ll take a journey through the most significant changes in each major LTS release:

Java 8 (2014):

  • Lambda Expressions: Introduced functional programming capabilities.
  • Stream API: Enabled efficient operations on collections using streams.
  • Default Methods: Allowed interfaces to have methods with implementations.
  • Optional Class: Provided a way to avoid NullPointerExceptions.
  • New Date/Time API: Introduced a more modern and immutable date/time API via the java.time package.

Java 11 (2018):

  • Local-Variable Syntax for Lambda Parameters: Enabled using the var keyword in lambda expressions.
  • HTTP Client API: Introduced a new API for handling HTTP/2 and WebSockets.
  • String Enhancements: New methods like isBlank(), lines(), repeat(), and strip().
  • File Methods: Added convenient methods like readString() and writeString() for file operations.
  • Removal of Java EE and CORBA Modules: Removed outdated technologies to streamline the platform.

Java 17 (2021):

  • Sealed Classes: Allowed restricted class hierarchies.
  • Pattern Matching for instanceof: Simplified instanceof checks by integrating pattern matching.
  • Records: Enabled a concise syntax for immutable data classes.
  • Text Blocks: Simplified the creation of multiline strings using triple quotes.
  • Strong Encapsulation in Modules: Enhanced encapsulation of internal APIs, improving modularity.

Java 21 (2023):

  • Pattern Matching for switch: Extended switch statements with pattern-matching capabilities.
  • Scoped Values: Provided an alternative to thread-local variables with more control over value propagation.
  • String Templates: Enabled dynamic string construction using embedded expressions inside string templates.
  • Sequenced Collections: Added new collection types (List, Set, Map) that maintain consistent element order.
  • Virtual Threads: Introduced virtual threads to allow efficient and lightweight concurrency.

In the following sections, we’ll dive deeper into these innovations, exploring how they have transformed Java programming over time and continue to make it one of the most robust and scalable languages available today.

Java 8: Ushering in Functional Programming

Released in 2014, Java 8 was a game-changer for the platform, introducing functional programming concepts that significantly improved how developers worked with collections and other data structures. Some of the standout features include:

Lambda Expressions

Lambda expressions brought functional programming to Java, making code more concise and expressive. Instead of writing anonymous inner classes, we could now define inline implementations of functional interfaces.

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));

Stream API

Java 8’s Stream API introduced a functional approach to processing collections, making it easier to work with large data sets in a declarative way.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.filter(n -> n % 2 == 0)
.mapToInt(n -> n)
.sum();
System.out.println(sum); // Output: 6

New Date and Time API

The java.time package in Java 8 provided a modern, immutable, and comprehensive approach to date and time handling, replacing the cumbersome java.util.Date.

LocalDate today = LocalDate.now();
LocalDate nextWeek = today.plusWeeks(1);
System.out.println(nextWeek);

Optional Class

The Optional class was introduced to help avoid NullPointerExceptions this by providing a container for values that may or may not be present. It encourages developers to handle absent values explicitly.

Optional<String> name = Optional.ofNullable("Alice");
name.ifPresent(System.out::println); // Output: Alice

Optional<String> empty = Optional.empty();
System.out.println(empty.isPresent()); // Output: false

Default Methods in Interfaces

Java 8 introduced default methods in interfaces, allowing interfaces to have method implementations. This enables developers to add new methods to interfaces without breaking existing implementations.

interface Vehicle {
default void start() {
System.out.println("Vehicle is starting");
}
}

class Car implements Vehicle { }

public class Main {
public static void main(String[] args) {
Vehicle car = new Car();
car.start(); // Output: Vehicle is starting
}
}

Java 11: A Step Towards Simplicity

Java 11, released in 2018, built on the foundations of Java 8 by streamlining certain aspects of the language and adding features that enhanced developer productivity.

HTTP Client API

Java 11 introduced a new HttpClient API, simplifying how we handle HTTP connections, including support for HTTP/2 and WebSockets.

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 Enhancements

Java 11 brought several new methods to the String class that simplified common string operations. Some of the most useful additions include:

  • isBlank(): Checks if the string is empty or contains only whitespace.
  • strip(): Removes leading and trailing whitespaces, including Unicode whitespaces.
  • lines(): Returns a stream of lines from a multi-line string.
  • repeat(): Repeats the string a specified number of times.
String str = "   Hello World!   ";
System.out.println(str.isBlank()); // Output: false
System.out.println(str.strip()); // Output: "Hello World!"
System.out.println("Java".repeat(3)); // Output: JavaJavaJava

Local-Variable Syntax for Lambda Parameters

Java 11 introduced the ability to use the var keyword in lambda expressions, which allowed for a more concise way to declare variable types in lambdas while maintaining readability.

var numbers = List.of(1, 2, 3);
numbers.forEach((var number) -> System.out.println(number));

File Methods (readString() and writeString())

Java 11 simplified file handling by introducing methods such as Files.readString() and Files.writeString(), which allows developers to read from and write to files with minimal code.

Path filePath = Paths.get("example.txt");
Files.writeString(filePath, "Hello, World!"); // Write string to file

String content = Files.readString(filePath); // Read string from file
System.out.println(content); // Output: Hello, World!

Removal of Java EE and CORBA Modules

Java 11 removed several outdated modules, including Java EE (Enterprise Edition) and CORBA (Common Object Request Broker Architecture), which were no longer relevant in modern Java applications. This streamlining made Java lighter and easier to maintain, focusing on core language and library improvements.

Java 17: Building Robust Applications with Modern Syntax

Java 17, an LTS release from 2021, focused on making Java cleaner and more powerful with new syntactic sugar and improvements to data structures.

Sealed Classes

Sealed classes allow developers to restrict which classes can extend or implement a specific class or interface, enhancing the control over inheritance hierarchies.

public abstract sealed class Shape permits Circle, Rectangle { }
final class Circle extends Shape { }
final class Rectangle extends Shape { }

Pattern Matching for instanceof

Java 17 simplifies type checking and casting with pattern matching for instanceof, reducing boilerplate code when working with different types.

Object obj = "Hello";
if (obj instanceof String str) {
System.out.println(str.toUpperCase()); // Output: HELLO
}

Records

Records are a new way to define simple data carrier classes, significantly reducing the boilerplate code needed for classes that only hold data.

public record Point(int x, int y) {}
Point point = new Point(10, 20);
System.out.println(point.x()); // Output: 10

Text Blocks

Java 17 officially incorporated text blocks introduced as a preview in Java 13. Text blocks simplify writing multiline strings using triple quotes (“”), making the code more readable.

String textBlock = """
Hello,
World!
This is a text block.
""";
System.out.println(textBlock);

Strong Encapsulation in Modules

Java 17 reinforced the module system by making internal APIs more strictly encapsulated. This ensures that only explicitly exported packages are accessible to other modules, improving security and modularity. Internal APIs that were previously accessible but not intended for public use are now strongly encapsulated, forcing developers to use well-defined APIs.

module mymodule {
exports com.mypackage; // Only exports public APIs
}

Java 21: The Future of Java is Here

Java 21, the latest LTS release (2023), marks a significant leap forward, particularly with concurrency and pattern-matching innovations.

Virtual Threads

With the introduction of virtual threads (part of Project Loom), Java 21 offers a new way to handle concurrency, drastically improving scalability and simplifying thread management.

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> System.out.println("Hello from a virtual thread!"));
}

Pattern Matching for switch

Java 21 enhances the switch statement with pattern matching, allowing developers to match on types, making the control flow much more powerful.

Object obj = 123;
String result = switch (obj) {
case Integer i -> "Integer: " + i;
case String s -> "String: " + s;
default -> "Unknown";
};
System.out.println(result); // Output: Integer: 123

String Templates

Java 21 introduces string templates, enabling more expressive and dynamic string construction with embedded expressions.

int x = 5;
String template = STR."Value of x is \{x}";
System.out.println(template); // Output: Value of x is 5

Scoped Values

Scoped Values are introduced as an alternative to thread-local variables. They allow for better control over the scope and propagation of values across execution contexts, providing a more structured way of managing state in concurrent applications.

ScopedValue<String> USER = ScopedValue.newInstance();

ScopedValue.where(USER, "Alice").run(() -> {
System.out.println(USER.get()); // Output: Alice
});

Sequenced Collections

Java 21 adds new collection types — SequencedCollection, SequencedSet, and SequencedMap—to ensure that collections maintain element order consistently across different implementations. This guarantees insertion order across List, Set, and Map collections.

SequencedSet<String> names = SequencedSet.of("Alice", "Bob", "Charlie");
names.forEach(System.out::println); // Output: Alice, Bob, Charlie
System.out.println("First: " + names.getFirst()); // Output: Alice
System.out.println("Last: " + names.getLast()); // Output: David
// Reverse iteration
names.reversed().forEach(System.out::println); // Output: David, Charlie, Bob, Alice


SequencedMap<Integer, String> ageMap = SequencedMap.of(1, "One", 2, "Two", 3, "Three");
ageMap.put(4, "Four");
ageMap.forEach((key, value) -> System.out.println(key + ": " + value));
// Output: 1: One, 2: Two, 3: Three, 4: Four
System.out.println("First Entry: " + ageMap.getFirst()); // Output: 1=One
System.out.println("Last Entry: " + ageMap.getLast()); // Output: 4=Four

// Reverse iteration
ageMap.reversed().forEach((key, value) -> System.out.println(key + ": " + value));

Conclusion

The journey from Java 8 to Java 21 showcases a clear progression toward making Java more concise, expressive, and powerful without sacrificing its stability and backward compatibility principles. Whether it’s the functional programming introduced in Java 8, the pattern matching and records of Java 17, or the groundbreaking virtual threads of Java 21, Java continues to evolve to meet the needs of modern developers.

As Java progresses, it remains one of the most trusted platforms for building scalable, maintainable, high-performance applications. The future is bright for developers who embrace these changes, and Java continues to be a leading force in software engineering.

Finally,

< Cheers, All is well, and always be happy, The Journey of Life/>

Buy me a coffee

--

--