Lỗi Gặp Phải
Ứng dụng của bạn bị crash ngay khi khởi động trước khi làm được bất cứ điều gì. Console hiển thị:
java.lang.ExceptionInInitializerError
at com.example.MyClass.<clinit>(MyClass.java:15)
Caused by: java.lang.NullPointerException
at com.example.MyClass.<clinit>(MyClass.java:12)
Ba điểm cho bạn biết chuyện gì đã xảy ra:
ExceptionInInitializerError— JVM không thể hoàn tất việc nạp một class.<clinit>— đây là class initializer. Một khốistatichoặc một phép gán fieldstaticđã bị lỗi.Caused by— vấn đề thực sự. Ở đây làNullPointerException, nhưng có thể là bất cứ thứ gì:IllegalArgumentException,IOException, hoặc lỗi từ chính code của bạn.
Nguyên Nhân
Lần đầu tiên JVM nạp một class, nó chạy tất cả các static initializer theo thứ tự khai báo — phép gán field inline trước, rồi đến các khối static { ... }. Nếu bất kỳ initializer nào ném ra unchecked exception (hoặc Error), JVM sẽ bọc nó trong ExceptionInInitializerError và đánh dấu vĩnh viễn class đó là bị lỗi.
Phần nguy hiểm là: mọi lần sử dụng class đó sau đó — kể cả bên trong catch block — đều ném ra NoClassDefFoundError. Bạn sẽ thấy hàng loạt lỗi dồn dập, tất cả đều bắt nguồn từ một static initializer bị lỗi. Hãy sửa nguồn gốc, không phải các lỗi kéo theo.
Các Nguyên Nhân Thường Gặp
- Đọc system property hoặc biến môi trường có giá trị
nulllúc khởi động, rồi ngay lập tức gọi phương thức trên nó - Gọi phương thức trên một đối tượng
staticchưa được khởi tạo - Parse một chuỗi hardcode (định dạng ngày tháng, số) hóa ra bị sai định dạng
- Đọc file hoặc classpath resource không tồn tại khi class được nạp lần đầu
- Hai class tham chiếu lẫn nhau qua các field
staticlúc nạp (circular initialization)
Cách Sửa Từng Bước
Bước 1 — Đọc Đúng Phần Stack Trace
Bỏ qua dòng ExceptionInInitializerError ở trên cùng. Kéo xuống đến Caused by và tìm số dòng trong file source của bạn.
Caused by: java.lang.NullPointerException
at com.example.MyClass.<clinit>(MyClass.java:12)
Mở MyClass.java, nhảy đến dòng 12. Đó là nơi xảy ra vấn đề.
Bước 2 — Xác Định Đoạn Static Code Bị Lỗi
Hai pattern phổ biến nhất:
// Pattern 1: static field inline
public class MyClass {
// System.getenv("DB_URL") có thể trả về null — gọi .trim() trên null sẽ crash ngay
private static final String DB_URL = System.getenv("DB_URL").trim();
}
// Pattern 2: static block
public class MyClass {
private static final Connection conn;
static {
conn = DriverManager.getConnection(DB_URL); // ném lỗi nếu DB_URL là null
}
}
Bước 3 — Thêm Kiểm Tra Null Hoặc Guard Lỗi
Kiểm tra giá trị trước khi dùng. Một thông báo lỗi rõ ràng sẽ giúp bạn tiết kiệm 20 phút debug về sau:
public class MyClass {
private static final String DB_URL;
static {
String url = System.getenv("DB_URL");
if (url == null || url.isBlank()) {
throw new IllegalStateException(
"Environment variable DB_URL is not set. " +
"Please configure it before starting the application."
);
}
DB_URL = url.trim();
}
}
Ném IllegalStateException từ static block vẫn tạo ra ExceptionInInitializerError. Nhưng lúc này thông điệp Caused by sẽ nói chính xác cái gì đang thiếu — không cần đoán mò nữa.
Bước 4 — Chuyển Khởi Tạo Ra Khỏi Static Context
Static initializer chạy một lần, âm thầm, trước khi app của bạn có thể bắt bất kỳ lỗi nào. Đọc file, gọi mạng, và nạp resource không nên ở đó. Lazy initialization an toàn hơn:
// Trước: static init có thể ném lỗi lúc nạp class
public class ConfigLoader {
private static final Properties props = loadProperties(); // crash xảy ra ở đây, trước main()
private static Properties loadProperties() {
// đọc config.properties — ném IOException nếu thiếu file
}
}
// Sau: trì hoãn đến lần dùng đầu tiên, xử lý lỗi rõ ràng
public class ConfigLoader {
private static Properties props;
public static synchronized Properties getProps() {
if (props == null) {
try {
props = loadProperties();
} catch (IOException e) {
throw new RuntimeException("Failed to load config.properties", e);
}
}
return props;
}
}
Bước 5 — Gỡ Bỏ Phụ Thuộc Static Vòng Tròn
Class A tham chiếu B.PREFIX. Class B tham chiếu A.NAME. Khi JVM nạp A trước, B chưa được khởi tạo — nên B.PREFIX là null. Cách sửa là tạo class thứ ba chứa các hằng số dùng chung:
// Bị lỗi: A nạp B, B nạp A — một cái thấy null
public class A {
public static final String NAME = B.PREFIX + "_A";
}
public class B {
public static final String PREFIX = A.NAME + "_B";
}
// Sửa: tách ra class Constants — không còn phụ thuộc vòng tròn
public class Constants {
public static final String PREFIX = "APP";
}
public class A {
public static final String NAME = Constants.PREFIX + "_A";
}
public class B {
public static final String NAME = Constants.PREFIX + "_B";
}
Bước 6 — Khi Class Bên Thứ Ba Bị Lỗi
Đôi khi <clinit> trỏ vào một thư viện, không phải code của bạn. Thường có nghĩa là thiếu file cấu hình hoặc classpath resource. Đọc toàn bộ chuỗi Caused by:
// Ví dụ: Log4j không tìm thấy config của nó
java.lang.ExceptionInInitializerError
at org.apache.logging.log4j.LogManager.<clinit>(LogManager.java:...)
Caused by: java.lang.IllegalStateException: No log4j2 configuration file found
Cách sửa ở đây là thêm log4j2.xml vào classpath của bạn — không cần thay đổi bất kỳ Java code nào.
Kiểm Tra Sau Khi Sửa
- Biên dịch lại và chạy lại.
ExceptionInInitializerErrorphải biến mất. - Nếu bạn đã thêm guard ném
IllegalStateException, hãy test: bỏ set biến môi trường và xác nhận bạn thấy thông báo tùy chỉnh của mình thay vì NPE trống không. - Chạy unit tests. Mọi test trước đây âm thầm không nạp được class sẽ pass — hoặc cho thông báo lỗi có thể đọc được.
# Smoke test sau khi sửa
$ java -cp . com.example.Main
# Kỳ vọng: app khởi động bình thường, không có ExceptionInInitializerError
# Test guard với biến môi trường rỗng
$ DB_URL= java -cp . com.example.Main
# Kỳ vọng: IllegalStateException: Environment variable DB_URL is not set.
Tóm Tắt Nhanh
- Đọc
Caused by, không phải dòng đầu —ExceptionInInitializerErrorchỉ là wrapper; exception thực sự nằm bên dưới. - Kiểm tra null cho biến môi trường và system property trước khi gọi bất kỳ phương thức nào trên chúng bên trong static block.
- Không đặt file I/O và network call trong static initializer — trì hoãn chúng vào các method có thể retry và bắt lỗi được.
- Phụ thuộc class vòng tròn — tách hằng số dùng chung ra class riêng để phá vỡ vòng lặp.
NoClassDefFoundErrorsau lần lỗi đầu tiên là triệu chứng kéo theo, không phải bug riêng biệt. Static initializer bị lỗi đầu tiên mới là thứ cần sửa.

