Hôm trước có bạn nhắn hỏi tôi một câu tưởng đơn giản: "Anh ơi em cài Java mà không biết tải cái nào, lên trang chủ thấy JDK, JRE, rồi lại OpenJDK, Temurin, Corretto, Zulu, mỗi thứ lại có mấy phiên bản. Tải nhầm có sao không?"

Câu hỏi đó xứng đáng có một câu trả lời tử tế, vì gần như ai học Java cũng vấp phải nó trong tuần đầu tiên, và cũng vì cách trả lời quen thuộc — "JDK là bộ phát triển, JRE là bộ chạy, JVM là máy ảo" — đúng nhưng chẳng giúp được gì. Nó giống như bảo rằng ô tô có động cơ, khung xe và bánh xe. Đúng cả, mà vẫn không biết nên mua xe nào.

Nên hôm nay ta mở nắp ca-pô ra xem.

Chuyện gì xảy ra khi bạn gõ java

Bắt đầu từ thứ cụ thể nhất. Tôi có một file duy nhất:

public class Xin {
    public static void main(String[] args) {
        System.out.println("Chào, đây là Java " + Runtime.version().feature());
    }
}

Và tôi chạy nó, không build tool, không project, không gì cả:

$ java Xin.java
Chào, đây là Java 21

Từ Java 11 trở đi, java nhận thẳng file .java — nó biên dịch trong bộ nhớ rồi chạy luôn, không để lại file .class nào trên đĩa. Rất tiện để thử một đoạn mã nhỏ. Nhưng đường đi truyền thống thì có hai bước, và hai bước này mới là chỗ để hiểu ba chữ viết tắt kia:

$ javac Xin.java
$ ls -l Xin.class
-rw-r--r-- 1 root root 1005 Aug  9 15:42 Xin.class

$ java Xin
Chào, đây là Java 21

javactrình biên dịch. Nó đọc mã nguồn và sinh ra Xin.class — 1005 byte. File đó không phải mã máy của CPU. Nó là bytecode: một thứ ngôn ngữ trung gian mà không con chip nào hiểu được trực tiếp.

javacỗ máy chạy bytecode đó. Nó nạp file .class, kiểm tra tính hợp lệ, rồi vừa chạy vừa dịch những đoạn nóng sang mã máy thật.

Toàn bộ kiến trúc Java nằm gọn trong chỗ tách đôi này. javac chỉ cần biết cú pháp Java. java mới là thứ phải biết mình đang chạy trên Linux hay Windows, trên chip Intel hay ARM. Bạn biên dịch một lần trên máy mình, file .class đó chạy được ở mọi nơi có java — đó chính là câu khẩu hiệu "viết một lần, chạy mọi nơi" mà Sun quảng cáo từ những năm 90, và nó không phải khẩu hiệu suông.

Ba lớp JDK, JRE và JVM lồng nhau

Ba chữ viết tắt, từ trong ra ngoài

Nhìn sơ đồ trên thì dễ nhớ hơn định nghĩa: ba thứ này lồng vào nhau, không phải ba thứ song song.

JVM là lõi trong cùng — cỗ máy thực thi. Nó có ba việc chính: nạp file .class vào bộ nhớ, chạy bytecode trong đó, và dọn rác cho bạn. Cái tên "máy ảo" khiến nhiều người tưởng nó giống VirtualBox hay VMware, nhưng không phải. Nó không giả lập một cái máy tính hoàn chỉnh. Nó chỉ là một chương trình biết đọc và thi hành một tập lệnh do người thiết kế ra thay vì do Intel thiết kế ra.

JRE là JVM cộng thêm thư viện chuẩn. String, ArrayList, HashMap, LocalDate — bạn không hề viết những lớp đó, chúng nằm sẵn trong JRE. Nếu chỉ có JVM trần thì bạn có cỗ máy chạy được bytecode nhưng chẳng có bytecode nào để chạy.

JDK là JRE cộng thêm bộ đồ nghề: javac để biên dịch, javap để đọc bytecode, jar để đóng gói, jcmdjfr để soi ứng dụng đang chạy, jshell để gõ thử một dòng lệnh, jlink để cắt một bản runtime tí hon.

Vì sao bây giờ không còn JRE để tải riêng

Đây là chỗ làm nhiều bài hướng dẫn cũ trở nên sai. Bạn tìm trên mạng, gặp bài viết bảo "người dùng cuối chỉ cần cài JRE, lập trình viên mới cần JDK", rồi vào trang tải thì chẳng thấy JRE đâu.

Vì từ Java 11, JRE không còn được phát hành riêng nữa. Nguyên nhân là hệ thống module ra đời ở Java 9. Thay vì đóng gói sẵn một bộ "runtime cho người dùng cuối", Oracle chọn hướng để bạn tự cắt lấy phần mình cần. JDK 21 có 69 module:

$ java --list-modules | wc -l
69

$ java --list-modules | head -5
java.base@21.0.11
java.compiler@21.0.11
java.datatransfer@21.0.11
java.desktop@21.0.11
java.instrument@21.0.11

java.base là module bắt buộc, chứa những thứ cơ bản nhất. Còn lại tuỳ nhu cầu. Với jlink bạn ghép đúng những module ứng dụng dùng thành một runtime riêng, thường chỉ 40–60 MB thay vì hơn 300 MB của cả JDK. Đây là lý do ảnh Docker của ứng dụng Java hiện đại nhẹ hơn hẳn so với năm bảy năm trước — chúng ta sẽ làm đúng việc này ở phần nói về Docker.

Nên câu trả lời thực dụng cho năm 2026: cứ cài JDK. Không có lý do gì để đi tìm JRE nữa.

OpenJDK, Temurin, Corretto — cùng một thứ hay khác nhau?

Đây mới là chỗ gây rối thật sự, và nó không phải chuyện kỹ thuật mà là chuyện giấy phép.

OpenJDK là mã nguồn. Nó là bản triển khai tham chiếu chính thức của Java, mã nguồn mở, giấy phép GPL. Gần như mọi thứ bạn nghe tên đều được biên dịch từ đúng mã nguồn đó.

Temurin (của Eclipse Adoptium), Corretto (của Amazon), Zulu (của Azul), Liberica (của BellSoft) — đều là các bản build từ OpenJDK. Khác nhau chủ yếu ở chỗ ai vá lỗi bảo mật, vá trong bao lâu, và có gói kèm gì thêm không. Về mặt ngôn ngữ và thư viện chuẩn thì như nhau.

Oracle JDK là bản của Oracle. Từ 2021, giấy phép NFTC cho phép dùng miễn phí kể cả trong sản xuất, nhưng chỉ trong vòng một năm sau khi phiên bản kế tiếp ra đời. Quá thời hạn đó mà vẫn muốn nhận bản vá thì phải trả tiền. Đây chính là cái bẫy hợp đồng khiến nhiều công ty phải rà lại toàn bộ máy chủ hồi 2019.

Lời khuyên gọn: dùng Temurin nếu bạn không có lý do gì khác. Nếu hệ thống chạy trên AWS thì Corretto là lựa chọn tự nhiên vì Amazon dùng chính nó cho dịch vụ của họ. Tránh Oracle JDK trừ khi công ty bạn đã mua hỗ trợ.

LTS: vì sao 21 chứ không phải 24

Java ra phiên bản mới mỗi sáu tháng. Nhưng chỉ vài phiên bản được gắn nhãn LTS — hỗ trợ dài hạn: Java 8, 11, 17, 21, và 25.

Khác biệt nằm ở thời gian nhận bản vá. Một phiên bản không phải LTS chỉ được vá trong sáu tháng, tới khi phiên bản kế tiếp ra thì thôi. LTS thì được vá nhiều năm — Temurin cam kết hỗ trợ Java 21 tới ít nhất năm 2029.

Với dự án thật, chuyện này không có gì phải cân nhắc: bám LTS. Không ai muốn nửa năm một lần lại phải nâng phiên bản Java trên toàn bộ máy chủ chỉ để tiếp tục nhận bản vá bảo mật.

$ java -version
openjdk version "21.0.11" 2026-04-21 LTS
OpenJDK Runtime Environment Temurin-21.0.11+10 (build 21.0.11+10-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.11+10 (build 21.0.11+10-LTS, mixed mode, sharing)

Ba dòng này đáng đọc kỹ vì chúng trả lời được kha khá câu hỏi khi bạn đi gỡ lỗi trên máy chủ lạ. 21.0.11 — phiên bản 21, bản vá thứ 11. Temurin — bản build của Adoptium. LTS — nằm trong diện hỗ trợ dài hạn. 64-Bit Server VM — dùng trình biên dịch C2, tối ưu cho ứng dụng chạy lâu. Còn mixed mode nghĩa là JVM vừa thông dịch vừa biên dịch nóng, thay vì thông dịch tất.

Cả loạt bài này dùng JDK 21. Mã trong bài sẽ chạy được trên 17 nếu không đụng tới tính năng mới, nhưng những phần về virtual thread hay pattern matching thì cần đúng 21 trở lên.

Cài thế nào cho đỡ khổ về sau

Cài Java bằng cách tải file về rồi bấm next-next là được, cho tới ngày bạn cần chạy đồng thời hai dự án trên hai phiên bản khác nhau. Ngày đó sẽ đến sớm hơn bạn nghĩ.

Nên tôi khuyên dùng SDKMAN ngay từ đầu, nếu bạn ở Linux hoặc macOS:

curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"

sdk install java 21.0.11-tem     # tem = Temurin
sdk use java 21.0.11-tem         # đổi phiên bản cho phiên làm việc hiện tại
sdk default java 21.0.11-tem     # đặt làm mặc định

Muốn xem có những bản nào thì sdk list java. Đổi qua lại giữa 17 và 21 chỉ mất một dòng lệnh, và mỗi cửa sổ terminal có thể dùng một phiên bản khác nhau. Trên Windows thì Scoop hoặc winget đóng vai trò tương tự.

Một điều nhỏ mà hay bị bỏ qua: kiểm tra JAVA_HOME có trỏ đúng chỗ không.

echo $JAVA_HOME

Rất nhiều lỗi kiểu "Maven build được mà IDE thì không" hoá ra chỉ vì hai bên nhìn vào hai JAVA_HOME khác nhau.

Đọng lại gì

Nếu chỉ nhớ được ba câu từ bài này, tôi mong đó là:

Bytecode là lý do Java chạy được mọi nơi — javac sinh ra nó, java thi hành nó, và chính chỗ tách đôi đó khiến JVM trở thành một nền tảng chứ không chỉ là công cụ cho một ngôn ngữ. Ngày nay Kotlin, Scala, Clojure và Groovy đều chạy trên cùng cỗ máy ấy.

JDK chứa JRE, JRE chứa JVM. Và từ Java 11 thì bạn cứ cài JDK, đừng đi tìm JRE nữa.

Chọn Temurin, chọn bản LTS. Hai quyết định đó tiết kiệm cho bạn rất nhiều thời gian tranh luận về sau.

Ngày mai chúng ta cài đặt thật, rồi chạy chương trình đầu tiên — và sẽ mổ luôn cái file .class 1005 byte kia ra xem bên trong có gì.