๐ Java Feature Evolution from Java 8 to Java 11 to Java 17 to Java 21 to Java 25 – Full Code Examples & Real Benefits
Weโll explore the most impactful developer-facing features from Java 8 to Java 11 to Java 17 to Java 21 to Java 25.
For each feature, youโll see:
- โ Modern Java code
- ๐ฐ๏ธ What the code looked like before the feature existed
- ๐ฟ The benefit explained in simple terms
๐ช Java 8 – The Start of Modern Java
1. Lambda Expressions
// โ
Java 8 and later
List<Integer> nums = Arrays.asList(1, 2, 3);
nums.forEach(n -> System.out.println(n));
๐ฐ๏ธ Before (Java 7):
List<Integer> nums = Arrays.asList(1, 2, 3);
for (Integer n : nums) {
System.out.println(n);
}
๐ฟ Benefit: Lambdas remove unnecessary code. Instead of writing full loops, you express what to do, not how to do it.
2. Stream API
// โ
Java 8+
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> result = nums.stream()
.filter(n -> n > 1)
.map(n -> n * 2)
.collect(Collectors.toList());
๐ฐ๏ธ Before (Java 7):
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> result = new ArrayList<>();
for (Integer n : nums) {
if (n > 1) {
result.add(n * 2);
}
}
๐ฟ Benefit: Streams make your intent crystal clear, avoid mutating lists manually, and reduce boilerplate for filtering, mapping, and collecting.
3. Optional
// โ
Java 8+
Optional<String> name = Optional.ofNullable(getName());
System.out.println(name.orElse("Unknown"));
๐ฐ๏ธ Before (Java 7):
String n = getName();
if (n == null) {
n = "Unknown";
}
System.out.println(n);
๐ฟ Benefit: Optional helps avoid NullPointerException
and clearly signals that a value might be missing.
4. Default Methods in Interfaces
// โ
Java 8+
interface Greeter {
default void greet() {
System.out.println("Hello!");
}
}
๐ฐ๏ธ Before (Java 7):
abstract class AbstractGreeter {
void greet() {
System.out.println("Hello!");
}
}
class MyGreeter extends AbstractGreeter {}
๐ฟ Benefit: No need to use abstract classes for shared behavior in interfaces. Easier evolution of APIs without breaking old code.
5. Date-Time API
// โ
Java 8+
LocalDate today = LocalDate.now();
System.out.println(today);
๐ฐ๏ธ Before (Java 7):
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
System.out.println(sdf.format(date));
๐ฟ Benefit: New API is immutable, thread-safe, and much easier to format/parse than java.util.Date
.
๐งญ Java 11 – Small but Mighty Improvements
1. var
in Lambda Parameters
// โ
Java 11
list.forEach((var s) -> System.out.println(s.toUpperCase()));
๐ฐ๏ธ Before (Java 8):
list.forEach((String s) -> System.out.println(s.toUpperCase()));
๐ฟ Benefit: Less clutter – cleaner lambdas, especially when adding annotations to parameters.
2. HTTP Client API
// โ
Java 11
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder(URI.create("https://example.com")).build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
๐ฐ๏ธ Before (Java 8):
URL url = new URL("https://example.com");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()))) {
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
}
๐ฟ Benefit: A modern, simpler HTTP API without clunky HttpURLConnection
and manual stream handling.
3. Launch Single-File Source Programs
# โ
Java 11
java Hello.java
๐ฐ๏ธ Before (Java 8):
javac Hello.java
java Hello
๐ฟ Benefit: Easier for quick scripts, demos, and testing without a build step.
๐ฆพ Java 17 – Pattern Matching, Records & Sealed Classes
1. Pattern Matching for instanceof
// โ
Java 17
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}
๐ฐ๏ธ Before (Java 8):
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
๐ฟ Benefit: No redundant casting. Code is cleaner and safer.
2. Records
// โ
Java 17
record Point(int x, int y) {}
Point p = new Point(1, 2);
System.out.println(p.x());
๐ฐ๏ธ Before (Java 8):
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
@Override
public boolean equals(Object o) { ... }
@Override
public int hashCode() { ... }
}
๐ฟ Benefit: Records cut tons of boilerplate for immutable data carriers.
3. Sealed Classes
// โ
Java 17
sealed interface Shape permits Circle, Square {}
final class Circle implements Shape {}
final class Square implements Shape {}
๐ฐ๏ธ Before (Java 8):
interface Shape {}
class Circle implements Shape {}
class Square implements Shape {}
// Anyone could extend Shape accidentally
๐ฟ Benefit: Control inheritance and maintain strict type hierarchies.
๐งต Java 21 – Virtual Threads, Patterns, String Templates
1. Virtual Threads
// โ
Java 21
Thread.startVirtualThread(() -> System.out.println("Virtual thread"));
๐ฐ๏ธ Before (Java 8):
new Thread(() -> System.out.println("Platform thread")).start();
๐ฟ Benefit: Virtual threads are lightweight – handle thousands of concurrent tasks without expensive OS threads.
2. Record Patterns
record Point(int x, int y) {}
Object o = new Point(3, 4);
if (o instanceof Point(int x, int y)) {
System.out.println(x + ", " + y);
}
๐ฐ๏ธ Before (Java 8):
if (o instanceof Point) {
Point p = (Point) o;
System.out.println(p.getX() + ", " + p.getY());
}
๐ฟ Benefit: Less casting, cleaner destructuring.
3. String Templates (Preview)
String name = "Alice";
String msg = STR."Hello, \{name}";
System.out.println(msg);
๐ฐ๏ธ Before (Java 8):
String msg = "Hello, " + name;
System.out.println(msg);
๐ฟ Benefit: More readable and secure string formatting.
4. Sequenced Collections
List<String> list = List.of("a", "b", "c");
System.out.println(list.getFirst());
System.out.println(list.getLast());
๐ฐ๏ธ Before (Java 8):
System.out.println(list.get(0));
System.out.println(list.get(list.size() - 1));
๐ฟ Benefit: Direct methods for first/last elements – cleaner, less error-prone.
๐งช Java 25 (Preview) – Even Less Boilerplate
1. Implicitly Declared Classes & Instance Main
// โ
Java 25
void main() {
System.out.println("Hello Java 25");
}
๐ฐ๏ธ Before:
public class Main {
public static void main(String[] args) {
System.out.println("Hello old Java");
}
}
๐ฟ Benefit: Less ceremony for simple programs and scripts.
2. Primitive Type Patterns in switch
Object o = 42;
switch (o) {
case int i -> System.out.println("Int: " + i);
case String s -> System.out.println("String: " + s);
default -> System.out.println("Other");
}
๐ฐ๏ธ Before:
if (o instanceof Integer) {
int i = (Integer) o;
System.out.println("Int: " + i);
} else if (o instanceof String) {
System.out.println("String: " + o);
} else {
System.out.println("Other");
}
๐ฟ Benefit: switch
becomes more powerful and expressive – less if-else nesting.
3. Stream Gatherers
List<Integer> nums = List.of(1, 2, 3, 4, 5);
nums.stream()
.gather(Gatherers.windowFixed(2))
.forEach(System.out::println);
๐ฐ๏ธ Before:
List<List<Integer>> windows = new ArrayList<>();
for (int i = 0; i < nums.size(); i += 2) {
windows.add(nums.subList(i, Math.min(i + 2, nums.size())));
}
windows.forEach(System.out::println);
๐ฟ Benefit: Easier to do advanced stream operations without custom collectors or manual loops.
๐ Conclusion: Why This Matters
- โ Modern Java is cleaner, safer, and more expressive.
- ๐งน Less boilerplate โ faster development.
- ๐ง Features like records, patterns, and virtual threads make your intent clear.
- ๐ Upgrading your Java version isnโt just about performance or security – itโs about developer productivity.
๐ If youโre still stuck on Java 8 or 11, nowโs the perfect time to start modernizing your codebase gradually.