Oct 3, 2024
Some Exception Handling Techniques in Java
Exception Handling - Basic
Occasionally, we might see scenarios like this:
1if (userRepository.existsByUsername(request.getUsername())) {
2 throw new RuntimeException("User existed");
3}
This could be improved by using a custom enum ErrorCode.java:
1@Getter
2@AllArgsConstructor
3public enum ErrorCode {
4 UNCATEGORIZED_EXCEPTION(9999, "Uncategorized error"),
5 INVALID_KEY(1001, "Invalid message key"),
6 USER_EXISTED(1002, "User existed"),
7 USERNAME_INVALID(1003, "Username must be at least 3 characters"),
8 INVALID_PASSWORD(1004, "Password must be at least 8 characters"),
9 USER_NOT_EXISTED(1005, "User not existed"),
10 UNAUTHENTICATED(1006, "Unauthenticated"),
11 ;
12
13 private int code;
14 private String message;
15}
Now we have an improvised version:
1if (userRepository.existsByUsername(request.getUsername())) {
2 throw new RuntimeException("ErrorCode.USER_EXISTED");
3}
The problem is, we cannot just throw a RuntimeException all the time. We can declare a custom exception:
1@Getter
2@Setter
3public class AppException extends RuntimeException {
4 private ErrorCode errorCode;
5
6 public AppException(ErrorCode errorCode) {
7 super(errorCode.getMessage());
8 this.errorCode = errorCode;
9 }
10}
Then we can “use” that exception now:
1if (userRepository.existsByUsername(request.getUsername())) {
2 throw new AppException(ErrorCode.USER_EXISTED);
3}
Normalized API response
We can create a class for API response:
1@Data
2@Builder
3@NoArgsConstructor
4@AllArgsConstructor
5@FieldDefaults(level = AccessLevel.PRIVATE)
6@JsonInclude(JsonInclude.Include.NON_NULL)
7public class ApiResponse <T> {
8 @Builder.Default
9 int code = 1000;
10 String message;
11 T result;
12}
Then we create a handler class:
1@ControllerAdvice
2public class GlobalExceptionHandler {
3
4 @ExceptionHandler(value = Exception.class)
5 ResponseEntity<ApiResponse> handleRuntimeException(RuntimeException exception) {
6 ApiResponse apiResponse = new ApiResponse();
7
8 apiResponse.setCode(ErrorCode.UNCATEGORIZED_EXCEPTION.getCode());
9 apiResponse.setMessage(ErrorCode.UNCATEGORIZED_EXCEPTION.getMessage());
10
11 // 400: error related data from user input
12 return ResponseEntity.badRequest().body(apiResponse);
13 }
14
15 @ExceptionHandler(value = AppException.class)
16 ResponseEntity<ApiResponse> handleAppException(AppException exception) {
17 ErrorCode errorCode = exception.getErrorCode();
18 ApiResponse apiResponse = new ApiResponse();
19 apiResponse.setCode(errorCode.getCode());
20 apiResponse.setMessage(errorCode.getMessage());
21 // 400: error related data from user input
22 return ResponseEntity.badRequest().body(apiResponse);
23 }
24
25 @ExceptionHandler(MethodArgumentNotValidException.class)
26 ResponseEntity<ApiResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException exception) {
27 String enumKey = exception.getFieldError().getDefaultMessage();
28
29 ErrorCode errorCode = ErrorCode.INVALID_KEY;
30 try {
31 errorCode = ErrorCode.valueOf(enumKey);
32 } catch (IllegalArgumentException e) {
33 }
34
35 ApiResponse apiResponse = new ApiResponse();
36 apiResponse.setCode(errorCode.getCode());
37 apiResponse.setMessage(errorCode.getMessage());
38
39 return ResponseEntity.badRequest().body(apiResponse);
40 }
41}
The third handler method is for validation. For example:
1@Data
2@Builder
3@NoArgsConstructor
4@AllArgsConstructor
5@FieldDefaults(level = AccessLevel.PRIVATE)
6public class UserRegisterRequest {
7 @Size(min = 3, message = "USERNAME_INVALID")
8 String username;
9 @Size(min = 8, message = "INVALID_PASSWORD")
10 String password;
11 @JsonProperty("first_name")
12 String firstName;
13 @JsonProperty("last_name")
14 String lastName;
15 @JsonProperty("date_of_birth")
16 LocalDate dateOfBirth;
17}
An example of using a normalized API response:
1@PostMapping
2ApiResponse<UserRegisterResponse> createUser(@RequestBody @Valid UserRegisterRequest request) {
3 ApiResponse<UserRegisterResponse> apiResponse = new ApiResponse<>();
4 apiResponse.setResult(userService.createUser(request));
5 return apiResponse;
6}
More about API response: currencylayer