Here's a concise explanation of how the flow works between UserException, ApplicationException, and GlobalExceptionHandler in your setup:
Your service or controller throws a domain-specific exception like:
package com.SyncMate.SyncMate.exception;
public class UserException extends ApplicationException{
public static final String USER_EXISTS = "USER_EXISTS";
public UserException(String errorCode, String message) {
super(errorCode, message);
}
// Helper factory methods
public static UserException userExists(String identifier) {
return new UserException(USER_EXISTS, "User with identifier " + identifier + " already exists");
}
}
throw UserException.userExists(email);
UserException Inherits from ApplicationExceptionUserException extends ApplicationException, which likely extends RuntimeException:
public class ApplicationException extends RuntimeException {
private final String errorCode;
public ApplicationException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
public ApplicationException(String errorCode, String message, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
public String getErrorCode() {
return errorCode;
}
}
So when UserException is thrown, it carries:
@ControllerAdviceSpring Boot automatically routes unhandled exceptions to your GlobalExceptionHandler, where you define:
@ExceptionHandler(UserException.class)
public ResponseEntity<ApiError> handleUserException(UserException ex) {
if (ex.getErrorCode().equals(UserException.USER_EXISTS)) {
log.warn("Registration attempt failed: {}", ex.getMessage());
}
HttpStatus status = mapErrorCodeToStatus(ex.getErrorCode());
ApiError error = buildApiError(status, ex.getMessage(), ex.getErrorCode());
return new ResponseEntity<>(error, status);
}
private HttpStatus mapErrorCodeToStatus(String errorCode) {
return switch(errorCode) {
case UserException.USER_EXISTS -> HttpStatus.CONFLICT;
};
}
private ApiError buildApiError(HttpStatus status, String message,
String errorCode) {
ApiError error = new ApiError();
error.setTimestamp(LocalDateTime.now());
error.setStatus(status.value());
error.setError(status.getReasonPhrase());
error.setMessage(message);
error.setErrorCode(errorCode);
return error;
}
Or for any ApplicationException:
@ExceptionHandler(ApplicationException.class)
public ResponseEntity<ApiError> handleApplicationException(ApplicationException ex) {
// This is a fallback for all other custom exceptions
}