Tóm tắt nhanh
Bạn đã gọi SomeEnum.valueOf(string) với một giá trị không khớp chính xác với tên hằng số enum nào — thường là do sai chữ hoa/thường, có khoảng trắng thừa, hoặc giá trị chưa được định nghĩa trong enum. Hãy bỏ valueOf() có sẵn và thêm một phương thức tra cứu không phân biệt hoa/thường vào enum của bạn.
// Dòng này sẽ ném exception nếu chuỗi không khớp chính xác:
Status status = Status.valueOf(input);
// Dòng này xử lý khác biệt hoa/thường và giá trị không xác định một cách an toàn:
Status status = Status.fromString(input);
Nguyên nhân gốc rễ
Enum.valueOf() thực hiện tra cứu tên theo kiểu phân biệt hoa/thường và hoàn toàn chính xác. Cách hoạt động này đã có từ Java 1.5 — đây là thiết kế có chủ đích, không phải lỗi. Ngay khi chuỗi đầu vào có sai lệch, bạn sẽ nhận được:
java.lang.IllegalArgumentException: No enum constant com.example.model.Status.UNKNOWN
Bốn kiểu đầu vào gây ra lỗi này trong 95% trường hợp:
- Một API bên ngoài gửi
"active"(chữ thường) nhưng hằng số của bạn làACTIVE - Một cột trong database lưu
"Pending"hoặc"pending"thay vì"PENDING" - Một API bên thứ ba đã thêm giá trị mới trước khi bạn kịp thêm vào model Java của mình
- Dữ liệu từ form nhập liệu chưa được chuẩn hóa trước khi chuyển đổi
Cách sửa 1: Thêm phương thức fromString() không phân biệt hoa/thường vào Enum
Hãy bắt đầu từ đây nếu bạn là người quản lý enum đó. Một phương thức fromString() tĩnh chỉ tốn khoảng 10 dòng code nhưng giải quyết triệt để vấn đề ở mọi nơi gọi đến — không còn phải rải try-catch khắp codebase nữa:
public enum Status {
ACTIVE, INACTIVE, PENDING;
public static Status fromString(String value) {
if (value == null) return null;
for (Status s : values()) {
if (s.name().equalsIgnoreCase(value.trim())) {
return s;
}
}
return null; // hoặc ném một exception theo nghiệp vụ của bạn tại đây
}
}
Cách dùng:
Status status = Status.fromString(input); // "active", "ACTIVE", " Active " đều được xử lý đúng
if (status == null) {
throw new IllegalArgumentException("Unrecognized status: " + input);
}
Cách sửa 2: Bọc try-catch (Vá nhanh tạm thời)
Chưa có thời gian refactor enum ngay bây giờ? Hãy bọc valueOf() trong try-catch và chuẩn hóa chuỗi trước:
public static Status parseStatus(String value) {
try {
return Status.valueOf(value.trim().toUpperCase());
} catch (IllegalArgumentException e) {
log.warn("Unknown status value received: {}", value);
return Status.PENDING; // giá trị dự phòng an toàn — cân nhắc kỹ khi chọn
}
}
.trim().toUpperCase() xử lý hai nguyên nhân phổ biến nhất chỉ trong một bước. Tuy nhiên, hãy cẩn thận khi chọn giá trị dự phòng. Việc mặc định về sai trạng thái một cách âm thầm có thể gây hỏng dữ liệu, còn khó debug hơn cả lỗi crash ban đầu.
Cách sửa 3: Deserialize với Jackson bằng @JsonCreator
Lỗi này xuất hiện bên trong Spring Boot REST controller? Thủ phạm là cách Jackson deserialize enum mặc định — nó gọi valueOf() bên dưới. Hãy chú thích một phương thức factory với @JsonCreator để kiểm soát quá trình này:
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
public enum Status {
ACTIVE, INACTIVE, PENDING;
@JsonCreator
public static Status fromJson(String value) {
if (value == null) return null;
try {
return Status.valueOf(value.toUpperCase().trim());
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid status: '" + value + "'");
}
}
@JsonValue
public String toJson() {
return name().toLowerCase();
}
}
Request body với {"status": "active"} giờ sẽ được deserialize mà không ném exception. Annotation @JsonValue giữ cho việc serialize nhất quán — API của bạn luôn ghi chữ thường trả về cho client.
Cách sửa 4: Ánh xạ Enum với JPA / Database bằng AttributeConverter
Các cột database là nguồn gây lỗi này rất thường gặp. Khi các chuỗi đã lưu như "pending" hoặc "Pending" không khớp với tên hằng số Java, hãy thêm một AttributeConverter để xử lý việc chuyển đổi theo cả hai chiều:
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
@Converter(autoApply = true)
public class StatusConverter implements AttributeConverter<Status, String> {
@Override
public String convertToDatabaseColumn(Status status) {
return status == null ? null : status.name().toLowerCase();
}
@Override
public Status convertToEntityAttribute(String dbData) {
return Status.fromString(dbData); // tái sử dụng phương thức từ Cách sửa 1
}
}
autoApply = true tự động áp dụng converter này cho mọi trường Status trong các JPA entity của bạn. Không cần thêm annotation @Convert cho từng trường nữa.
Cách sửa 5: Dùng EnumUtils của Apache Commons Lang
Dự án của bạn đã có sẵn Commons Lang? EnumUtils.getEnum() trả về null khi không tìm thấy thay vì ném exception. Lưu ý một điểm: phương thức này vẫn phân biệt hoa/thường, vì vậy hãy xử lý chuỗi trước nếu bạn cần so sánh không phân biệt hoa/thường:
import org.apache.commons.lang3.EnumUtils;
Status status = EnumUtils.getEnum(Status.class, input.trim().toUpperCase());
if (status == null) {
// xử lý giá trị không xác định
}
Dependency Maven:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.14.0</version>
</dependency>
Kiểm tra
Đừng bỏ qua bước viết test. Hãy bao phủ chính xác những đầu vào đã làm sập production — chữ thường, chữ hoa, hỗn hợp, khoảng trắng đầu/cuối, null, và các giá trị không xác định:
@Test
void testFromString() {
assertEquals(Status.ACTIVE, Status.fromString("active"));
assertEquals(Status.ACTIVE, Status.fromString("ACTIVE"));
assertEquals(Status.ACTIVE, Status.fromString(" Active "));
assertNull(Status.fromString("UNKNOWN")); // giá trị không xác định trả về null
assertNull(Status.fromString(null)); // đầu vào null được xử lý an toàn
}
Đối với các cách sửa liên quan đến JSON, hãy thêm một integration test cho controller. Gửi đúng payload đã gây lỗi và kiểm tra response trả về 200, không phải 500.
Nên chọn cách sửa nào
Hãy chọn dựa vào nguồn gốc của chuỗi đầu vào:
- Enum dùng chung trong codebase của bạn → Cách sửa 1. Đơn giản, dễ test, tái sử dụng được ở khắp nơi.
- Spring Boot REST API nhận JSON → Cách sửa 3 (
@JsonCreator). Jackson tự động nhận diện và áp dụng. - JPA entity được ánh xạ với cột kiểu chuỗi → Cách sửa 4 (AttributeConverter). Một converter xử lý toàn bộ các entity mapping.
- Vá khẩn cấp khi đang áp lực thời gian → Cách sửa 2 (try-catch). Tốt hơn là không làm gì — nhớ quay lại xử lý đúng cách sau khi qua giai đoạn khủng hoảng.

