One should try to predicts the failure points as much as possible at-least while programming

graph LR
A[Errors In Controller] --> B[catched by controller Advice]
A --> A2[Type Of Error]
A2 --> E1[Business Logic Error]
A2 --> E2[Server Error eg db, redis, client server error ]

E1 --> E11[Directly Send to user mostly no need for alert]
E2 --> E21[Need to setup alert as this is system's fault]



🧾 Error Classification & HTTP Status Codes

🔹 1. Business Errors (4xx)

Errors due to invalid client input or known domain logic.

TypeHTTP CodeNotes
Validation Failed400Input field errors
Missing or Invalid Params400Query/body/path issues
Unauthorized401Auth token missing/invalid
Forbidden403Authenticated but not authorized
Resource Not Found404Entity does not exist
Conflict / Duplicate409Already exists (e.g. email taken)
Rate Limit Exceeded429Too many requests

No alert needed. Log as INFO or WARN.


🔹 2. System Errors (5xx)

Failures due to infra, network, or unhandled conditions.

TypeHTTP CodeNotes
Internal Server Error500Catch-all for unhandled exceptions
Database Failure500Connectivity/query failure
External Service Failure502/504Downstream API failed / timeout
Dependency Timeout504Redis/DB/service timeout

Alert required. Log as ERROR. Include traceId.


🔹 3. Custom Recommendation: can be implemented Further

  • Always return structured JSON:

    {
      "status": 500,
      "error": "Internal Server Error",
      "message": "Database unavailable",
      "traceId": "abc123"
    }
  • Include traceId (MDC/Sleuth) for log correlation.

Code

GlobalExceptionHandler

 
@RestControllerAdvice
public class GlobalExceptionHandler {
 
  private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
 
  @ExceptionHandler(Exception.class)
  public ResponseEntity<ErrorResponseDto> handleGenericException(
      Exception ex, WebRequest request) {
 
    logger.error("Unexpected error occurred: ", ex);
 
    logger.error("Caught in global: {}", ex.getClass().getSimpleName(), ex);
 
    ErrorResponseDto errorResponse = new ErrorResponseDto(
        "An unexpected error occurred ",
        "INTERNAL_SERVER_ERROR",
        HttpStatus.INTERNAL_SERVER_ERROR.value());
 
    return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
  }
 
  @ExceptionHandler(UserNotFoundException.class)
  public ResponseEntity<ErrorResponseDto> handleUserNotFoundException(
      UserNotFoundException ex, WebRequest request) {
 
    logger.warn("User not found: {}", ex.getMessage());
 
    ErrorResponseDto errorResponse = new ErrorResponseDto(
        ex.getMessage(),
        "USER_NOT_FOUND",
        HttpStatus.NOT_FOUND.value());
 
    return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND);
  }
 
 
  @ExceptionHandler(UserAlreadyExistsException.class)
  public ResponseEntity<ErrorResponseDto> handleUserAlreadyExistsException(
      UserAlreadyExistsException ex, WebRequest request) {
    logger.warn("User already exists: {}", ex.getMessage());
 
    ErrorResponseDto errorResponse = new ErrorResponseDto(
        ex.getMessage(),
        "USER_ALREADY_EXISTS",
        HttpStatus.CONFLICT.value());
 
    return new ResponseEntity<>(errorResponse, HttpStatus.CONFLICT);
  }
 
 
  @ExceptionHandler(InvalidParamsProvidedException.class)
  public ResponseEntity<ErrorResponseDto> handleInValidParamsErrorException(InvalidParamsProvidedException ex,
                                                                           WebRequest request) {
    logger.warn("Invalid Email Provided : {}", ex.getMessage());
 
    ErrorResponseDto errorResponse = new ErrorResponseDto(
        ex.getMessage(),
        "INVALID_PARAMS",
        HttpStatus.UNAUTHORIZED.value());
    return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
  }
 
}
 

Error Definition

package io.saanvi.saanvibackend.core.exception.common;
 
public class InvalidParamsProvidedException extends RuntimeException {
    public InvalidParamsProvidedException(String message) {
        super(message);
    }
}
 

How to use

    public User findUserById(String Id) throws Exception {
        Optional<User> user = userRepository.findById(Id);
 
        if (user.isEmpty())
            throw new UserNotFoundException("User Not Found");
 
        return user.get();
 
    }