Vấn đề
Nếu bạn đã làm việc với Java một thời gian, chắc chắn đã từng gặp câu hỏi: “Dự án này nên dùng Java version mấy?”
Không phải ai cũng có thời gian theo dõi từng bản release. Bài viết này sẽ tóm tắt những tính năng quan trọng nhất của 4 phiên bản LTS phổ biến nhất — Java 8, 11, 17, 21 — và giúp bạn hiểu rõ mỗi version đem lại gì, để từ đó chọn đúng version cho dự án của mình.
- LTS (Long-Term Support) là các phiên bản được Oracle hỗ trợ bảo mật và bản vá trong nhiều năm, phù hợp để dùng trong môi trường production.
Java 8 (2014) — Cuộc Cách Mạng Functional
Java 8 là bản ngoặt lớn nhất trong lịch sử Java. Trước Java 8, code Java bị chê là dài dòng và verbose. Sau Java 8, nhiều thứ thay đổi hoàn toàn.
Lambda Expression
Trước Java 8, để tạo một Comparator, bạn phải viết:
// Trước Java 8
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
Sau Java 8:
// Java 8+
List<String> names = Arrays.asList("Charlie", "Alice", "Bob");
names.sort((a, b) -> a.compareTo(b));
// Hoặc gọn hơn với method reference
names.sort(String::compareTo);
Lambda giúp code ngắn hơn đáng kể, đặc biệt khi làm việc với các functional interface.
Stream API
Stream API là tính năng được dùng nhiều nhất trong Java 8. Thay vì viết vòng lặp thủ công, bạn có thể xử lý collection theo kiểu declarative:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// Lọc tên dài hơn 4 ký tự, viết hoa, rồi in ra
names.stream()
.filter(name -> name.length() > 4)
.map(String::toUpperCase)
.forEach(System.out::println);
// Kết quả: CHARLIE, DAVID
Optional
Optional ra đời để giải quyết vấn đề NullPointerException — một trong những lỗi phổ biến nhất trong Java:
// Trước Java 8 — dễ quên check null
String username = user.getUsername();
System.out.println(username.toUpperCase()); // NPE nếu username là null
// Java 8+
Optional<String> username = Optional.ofNullable(user.getUsername());
username.ifPresent(name -> System.out.println(name.toUpperCase()));
Default Method trong Interface
Trước Java 8, interface không thể có implementation. Java 8 cho phép điều này với default method:
public interface Greeting {
String getName();
default void sayHello() {
System.out.println("Xin chào, " + getName());
}
}
Đây là lý do Oracle có thể thêm hàng loạt method mới vào Collection, List, Map… mà không phá vỡ backward compatibility.
Java 11 (2018) — Dọn Dẹp và Tinh Gọn
Java 11 là LTS tiếp theo sau Java 8, mang theo một loạt cải tiến nhỏ nhưng hữu ích trong cuộc sống hàng ngày.
Chạy file Java không cần compile
Trước Java 11, để chạy một file .java, bạn cần compile trước:
# Trước Java 11
javac Hello.java
java Hello
Từ Java 11:
# Java 11+
java Hello.java
Rất tiện khi cần chạy nhanh một script nhỏ.
String Methods mới
Java 11 thêm một số method mới cho String mà trước đây bạn hay phải dùng thư viện ngoài:
// isBlank() — check chuỗi rỗng hoặc chỉ có whitespace
" ".isBlank(); // true
"hi".isBlank(); // false
// strip() — giống trim() nhưng hỗ trợ Unicode whitespace
" hello ".strip(); // "hello"
" hello ".stripLeading(); // "hello "
" hello ".stripTrailing(); // " hello"
// lines() — tách chuỗi theo dòng, trả về Stream
"line1\nline2\nline3".lines()
.forEach(System.out::println);
// repeat() — lặp chuỗi n lần
"ha".repeat(3); // "hahaha"
var trong Lambda (Java 11)
Java 10 đã giới thiệu var cho local variable. Java 11 mở rộng cho phép dùng var trong lambda parameter:
// Java 11
var list = List.of("Alice", "Bob", "Charlie");
list.stream()
.map((var name) -> name.toUpperCase())
.forEach(System.out::println);
HTTP Client mới (chuẩn)
Java 11 chính thức đưa HttpClient vào standard library, thay thế HttpURLConnection cũ kỹ:
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Java 17 (2021) — Sealed Classes và Pattern Matching
Java 17 đánh dấu bước chuyển lớn về type system, giúp code an toàn hơn và expressive hơn.
Sealed Classes
Sealed class cho phép bạn kiểm soát chính xác những class nào được phép kế thừa:
public sealed class Shape
permits Circle, Rectangle, Triangle {
}
public final class Circle extends Shape {
double radius;
}
public final class Rectangle extends Shape {
double width, height;
}
public final class Triangle extends Shape {
double base, height;
}
Điều này hữu ích khi bạn muốn đảm bảo không ai tự ý tạo thêm subclass ngoài những class đã định sẵn — rất phổ biến khi mô hình hóa domain model.
Pattern Matching cho instanceof
Trước Java 17, khi dùng instanceof, bạn phải cast thủ công:
// Trước Java 16
if (shape instanceof Circle) {
Circle c = (Circle) shape; // cast thủ công
System.out.println("Bán kính: " + c.radius);
}
Từ Java 16 (chính thức trong 17):
// Java 17
if (shape instanceof Circle c) {
System.out.println("Bán kính: " + c.radius);
}
Kết hợp với sealed class, bạn có thể dùng switch để xử lý tất cả các case một cách type-safe:
double area = switch (shape) {
case Circle c -> Math.PI * c.radius * c.radius;
case Rectangle r -> r.width * r.height;
case Triangle t -> 0.5 * t.base * t.height;
};
Text Blocks (chính thức từ Java 15, ổn định trong 17)
Viết chuỗi nhiều dòng như JSON, SQL, HTML giờ không còn phải nối chuỗi loằng ngoằng:
// Trước đây
String json = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 30\n" +
"}";
// Java 17+
String json = """
{
"name": "Alice",
"age": 30
}
""";
Records
Record là cách khai báo data class ngắn gọn, tự động sinh constructor, getter, equals(), hashCode(), toString():
// Trước đây — phải viết rất nhiều boilerplate
public class Point {
private final int x;
private final int y;
public Point(int x, int y) { this.x = x; this.y = y; }
public int x() { return x; }
public int y() { return y; }
// equals, hashCode, toString...
}
// Java 17+
public record Point(int x, int y) {}
// Dùng như bình thường
Point p = new Point(3, 4);
System.out.println(p.x()); // 3
System.out.println(p); // Point[x=3, y=4]
Java 21 (2023) — Virtual Threads và Pattern Matching Hoàn Chỉnh
Java 21 là LTS mới nhất và mang theo những thay đổi lớn nhất về concurrency kể từ Java 5.
Virtual Threads (Project Loom)
Đây là tính năng được chờ đợi nhất. Virtual threads là thread cực kỳ nhẹ do JVM quản lý, không phải OS thread.
Trước Java 21, mỗi thread Java tương ứng với một OS thread, tốn khoảng 1–2MB stack memory. Tạo hàng nghìn thread là không thực tế. Vì vậy các ứng dụng phải dùng async/reactive để scale.
Với virtual threads:
// Java 21 — tạo 100,000 virtual threads thoải mái
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 100_000).forEach(i ->
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
System.out.println("Task " + i + " done");
})
);
}
100,000 virtual threads chiếm bộ nhớ ít hơn nhiều so với 100,000 OS threads. Và bạn vẫn viết code theo kiểu blocking bình thường — không cần CompletableFuture hay reactive library.
Sequenced Collections
Java 21 thêm interface SequencedCollection vào Collections framework, chuẩn hóa cách truy cập phần tử đầu/cuối:
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
// Trước Java 21 — không nhất quán giữa các collection
list.get(0); // first element của List
list.get(list.size() - 1); // last element — verbose
deque.peekFirst(); // first element của Deque
deque.peekLast(); // last element của Deque
// Java 21 — nhất quán với mọi SequencedCollection
list.getFirst(); // "a"
list.getLast(); // "c"
list.reversed(); // ["c", "b", "a"]
Pattern Matching cho Switch (chính thức)
Tính năng preview từ Java 17–20, đến Java 21 chính thức stable:
static String describe(Object obj) {
return switch (obj) {
case Integer i when i < 0 -> "Số âm: " + i;
case Integer i -> "Số nguyên: " + i;
case String s -> "Chuỗi: " + s;
case null -> "Null";
default -> "Kiểu khác";
};
}
System.out.println(describe(-5)); // Số âm: -5
System.out.println(describe(42)); // Số nguyên: 42
System.out.println(describe("hi")); // Chuỗi: hi
Chú ý when — đây là guarded pattern, cho phép thêm điều kiện vào từng case.
Record Patterns
Kết hợp pattern matching với record để destructure trực tiếp:
record Point(int x, int y) {}
record Line(Point start, Point end) {}
static void printLine(Object obj) {
if (obj instanceof Line(Point(int x1, int y1), Point(int x2, int y2))) {
System.out.printf("Từ (%d,%d) đến (%d,%d)%n", x1, y1, x2, y2);
}
}
So Sánh Tổng Quan
| Tính năng | Java 8 | Java 11 | Java 17 | Java 21 |
|---|---|---|---|---|
| Lambda & Stream | ✅ | ✅ | ✅ | ✅ |
| Optional | ✅ | ✅ | ✅ | ✅ |
var keyword |
❌ | ✅ | ✅ | ✅ |
| HTTP Client mới | ❌ | ✅ | ✅ | ✅ |
| Text Blocks | ❌ | ❌ | ✅ | ✅ |
| Records | ❌ | ❌ | ✅ | ✅ |
| Sealed Classes | ❌ | ❌ | ✅ | ✅ |
Pattern Matching instanceof |
❌ | ❌ | ✅ | ✅ |
Pattern Matching switch |
❌ | ❌ | ❌ | ✅ |
| Virtual Threads | ❌ | ❌ | ❌ | ✅ |
| Sequenced Collections | ❌ | ❌ | ❌ | ✅ |
Nên Dùng Version Nào?
- Java 8 — Nếu bạn đang maintain dự án cũ chưa thể migrate, hoặc đang học Java từ đầu và cần hiểu nền tảng
- Java 11 — Nếu dự án đang dùng Java 8 và cần nâng cấp nhẹ, môi trường chưa sẵn sàng cho Java 17+
- Java 17 — Lựa chọn an toàn cho dự án mới hiện tại: nhiều tính năng hiện đại, được hỗ trợ đến 2029, và hầu hết framework lớn (Spring Boot 3, Quarkus) đều yêu cầu Java 17 trở lên
- Java 21 — Nếu bạn xây dựng ứng dụng cần xử lý nhiều concurrent requests (API server, microservice) và muốn tận dụng Virtual Threads thay vì reactive programming