What are the biggest language changes in modern Java, and which ones would you actually use in day-to-day code?
The headline changes fall into three groups. The first wave gave the language lambdas, method references, the Stream API, the modern date and time library and Optional — the single largest jump in how ordinary Java is written. The second wave modernised the platform: the module system, local variable type inference with var, switch expressions, text blocks, records, pattern matching for instanceof and sealed classes. The third wave completed that work with pattern matching for switch and virtual threads, which changed how concurrent Java is written. The sample above is written with the features every recent JDK supports, so you can run it as it is. The sample above is written with the features every recent JDK supports, so you can run it as it is.
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
// lambdas and streams.
var names = List.of("Ada", "Grace", "Alan");
String upper = names.stream()
.map(String::toUpperCase)
.collect(Collectors.joining(", "));
System.out.println("upper : " + upper);
// var for local type inference.
var counts = names.stream()
.collect(Collectors.groupingBy(String::length, Collectors.counting()));
System.out.println("by length: " + counts);
// switch expressions with multiple labels per case.
int day = 3;
String kind = switch (day) {
case 1, 7 -> "weekend";
case 2, 3, 4, 5, 6 -> "weekday";
default -> "unknown";
};
System.out.println("day " + day + " : " + kind);
// Optional, to keep the null check out of the caller.
java.util.Optional<String> maybe = java.util.Optional.of("present");
System.out.println("optional : " + maybe.orElse("absent"));
}
}The output shows one representative feature from each era working together in a single class.
If you are asked what you would use daily, the honest answer is: lambdas and streams, var, switch expressions, records, text blocks, and pattern matching for instanceof. The others — modules, sealed types, virtual threads — matter for particular kinds of work and are worth knowing about rather than using everywhere.
Two things that are easy to get wrong. var is for local variables only, and it infers the type rather than making it dynamic — the variable is still statically typed. And switch expressions differ from switch statements in that they are exhaustive: over an enum they must cover every constant, and adding a constant to the enum turns a missing branch into a compile error.