Here's a concise explanation of how the flow works between UserException, ApplicationException, and GlobalExceptionHandler in your setup:


🔁 Flow Overview

1. Custom Exceptions Are Thrown

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);

2. UserException Inherits from ApplicationException

UserException 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:

3. Spring Catches It in @ControllerAdvice

Spring 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
}