Understanding Java Exceptions and Custom Assertions for Elegant Error Handling
This article explains Java's Error and Exception hierarchy, differentiates compile-time and runtime exceptions, demonstrates standard try‑catch handling, shows how to throw custom exceptions, and introduces custom assertion interfaces that combine readable assertions with tailored exception types for cleaner backend code.
Java categorizes throwable objects into two main branches: Error , representing fatal system or JVM issues that cannot be recovered by the program, and Exception , representing anticipated problems such as I/O failures, null pointers, or index out‑of‑bounds errors. Exceptions are further divided into checked (compile‑time) exceptions like IOException and SQLException , which the compiler forces you to handle, and unchecked (runtime) exceptions like NullPointerException and IndexOutOfBoundsException , which should be exposed during testing.
Typical exception handling in Java uses try‑catch blocks. Simple examples include catching a generic Exception or more specific types such as IOException :
try {
// business logic
} catch (Exception e) {
// handle generic exception
} try {
// business logic
} catch (IOException ie) {
// handle I/O exception
} catch (Exception e) {
// handle other exceptions
}Throwing exceptions can simplify flow control by propagating errors to a centralized gateway, but using many custom exception classes can become cumbersome. An alternative is to use assertions for validation, though plain assertions lack the ability to convey distinct error codes.
To combine readability with precise error categorization, the article proposes a custom assertion mechanism. When an assertion fails, it throws a predefined custom exception. The core components include:
@Getter
@Setter
public class BaseException extends RuntimeException {
// response code enum
private IResponseEnum responseEnum;
// additional parameters
private Object[] objs;
public BaseException(String message, IResponseEnum responseEnum, Object[] objs) {
super(message);
this.responseEnum = responseEnum;
this.objs = objs;
}
public BaseException(String message, Throwable cause, IResponseEnum responseEnum, Object[] objs) {
super(message, cause);
this.responseEnum = responseEnum;
this.objs = objs;
}
} public interface MyAssert {
BaseException newException(Object... objs);
BaseException newException(String msg, Object... objs);
BaseException newException(Throwable t, String msg, Object... objs);
default void assertNotNull(Object obj, Object... objs) {
if (obj == null) {
throw newException(objs);
}
}
default void assertNotNull(Object obj, String msg, Object... objs) {
if (obj == null) {
throw newException(msg, objs);
}
}
}A concrete business exception and response enum are defined, allowing enumeration of error codes and messages:
public class BusinessException extends BaseException {
public BusinessException(IResponseEnum responseEnum, Object[] args, String msg) {
super(msg, responseEnum, args);
}
public BusinessException(IResponseEnum responseEnum, Object[] args, String msg, Throwable t) {
super(msg, t, responseEnum, args);
}
} public interface IResponseEnum {
String getCode();
String getMsg();
} public interface BusinessExceptionAssert extends IResponseEnum, MyAssert {
@Override
default BaseException newException(Object... args) {
return new BusinessException(this, args, this.getMsg());
}
@Override
default BaseException newException(String msg, Object... args) {
return new BusinessException(this, args, msg);
}
@Override
default BaseException newException(Throwable t, String msg, Object... args) {
return new BusinessException(this, args, msg, t);
}
} public enum ResponseEnum implements IResponseEnum, BusinessExceptionAssert {
BAD_LICENCE("0001", "无权访问"),
USER_NOT_FOUND("1001", "用户不存在");
private final String code, msg;
ResponseEnum(String code, String msg) { this.code = code; this.msg = msg; }
@Override public String getCode() { return code; }
@Override public String getMsg() { return msg; }
}Usage example shows a service method asserting the existence of a user entity; if the assertion fails, ResponseEnum.USER_NOT_FOUND triggers a BusinessException with the appropriate code and message:
@Override
public UserVO queryUser(Long id) {
UserDO userDO = userMapper.queryUserById(id);
ResponseEnum.USER_NOT_FOUND.assertNotNull(userDO); // throws custom exception if null
return userDO.toVo();
}A global exception handler catches BusinessException and returns a unified response containing the error code and message:
@ControllerAdvice
public class BusinessExceptionHandler {
@ExceptionHandler(BusinessException.class)
@ResponseBody
public Response handBusinessException(BaseException e) {
return new Response(e.getResponseEnum().getCode(), e.getResponseEnum().getMsg());
}
}By integrating custom assertions with enumerated error codes, developers gain both high readability and precise error categorization, and extending the system with new error scenarios only requires adding new entries to the ResponseEnum .
JD Retail Technology
Official platform of JD Retail Technology, delivering insightful R&D news and a deep look into the lives and work of technologists.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.