Some Exception Handling Techniques in Java

Le Quang Truong

javaspring

425 Words 1 Minute, 55 Seconds

2024-10-02 18:59 +0000


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