Best Practices for Returning Error Messages in Spring MVC Controllers

Nov 25, 2025 · Programming · 8 views · 7.8

Keywords: Spring MVC | Exception Handling | ResponseEntity | @ControllerAdvice | Error Messages

Abstract: This article provides an in-depth exploration of two primary methods for handling exceptions and returning error messages in Spring MVC controllers: using ResponseEntity to directly return error information and implementing global exception handling through @ControllerAdvice. The analysis covers implementation details, applicable scenarios, and best practices, including code examples, performance considerations, and practical application recommendations. Based on highly-rated Stack Overflow answers and Spring official documentation, it offers comprehensive and practical technical guidance.

Introduction

Exception handling is a critical aspect of Spring MVC application development. When exceptions occur during controller method execution, developers must address how to gracefully return error information to clients while maintaining code cleanliness and maintainability. This article provides a thorough analysis of two mainstream exception handling approaches based on practical development experience and best practices from the technical community.

Direct Error Message Return with ResponseEntity

The first approach involves handling exceptions directly within controller methods and returning specific error messages through ResponseEntity. This method is suitable for scenarios requiring fine-grained exception handling for specific exceptions.

In the original code example, the developer used traditional try-catch blocks to catch CheckingCredentialsFailedException but only returned HTTP status codes and null objects:

@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<UserWithPhoto> getUser(@RequestHeader(value="Access-key") String accessKey,
                                     @RequestHeader(value="Secret-key") String secretKey){
    try{
        return new ResponseEntity<UserWithPhoto>((UserWithPhoto)this.userService.chkCredentials(accessKey, secretKey, timestamp),
                new HttpHeaders(),
                HttpStatus.CREATED);
    }
    catch(ChekingCredentialsFailedException e){
        e.printStackTrace();
        return new ResponseEntity<UserWithPhoto>(null,new HttpHeaders(),HttpStatus.FORBIDDEN);
    }
}

The improved code utilizes ResponseEntity's builder pattern to return specific error messages when exceptions occur:

@RequestMapping(method = RequestMethod.GET)
public ResponseEntity getUser(@RequestHeader(value="Access-key") String accessKey,
                              @RequestHeader(value="Secret-key") String secretKey) {
    try {
        return ResponseEntity
            .status(HttpStatus.CREATED)                 
            .body(this.userService.chkCredentials(accessKey, secretKey, timestamp));
    }
    catch(ChekingCredentialsFailedException e) {
        // Use logger instead of printStackTrace
        logger.error("Credentials validation failed", e);
        return ResponseEntity
            .status(HttpStatus.FORBIDDEN)
            .body("Authentication failed: Invalid credentials provided");
    }
}

This approach offers clear code logic with tightly coupled exception handling and business logic. The ResponseEntity builder provides better readability and explicitly specifies HTTP status codes and response body content. It's important to note that in production environments, loggers (such as SLF4J) should be used to record exception information instead of printStackTrace method.

Global Exception Handling with @ControllerAdvice

The second approach utilizes Spring's @ControllerAdvice annotation for global exception handling. This method separates exception handling logic from controllers, achieving better separation of concerns and code reusability.

First, remove the try-catch block from the controller method and let exceptions propagate naturally:

@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<UserWithPhoto> getUser(@RequestHeader(value="Access-key") String accessKey,
                                     @RequestHeader(value="Secret-key") String secretKey) {
    UserWithPhoto user = (UserWithPhoto)this.userService.chkCredentials(accessKey, secretKey, timestamp);
    return ResponseEntity
        .status(HttpStatus.CREATED)
        .body(user);
}

Then create a global exception handling class:

@ControllerAdvice
public class ExceptionHandlerAdvice {

    private static final Logger logger = LoggerFactory.getLogger(ExceptionHandlerAdvice.class);

    @ExceptionHandler(ChekingCredentialsFailedException.class)
    public ResponseEntity handleException(ChekingCredentialsFailedException e) {
        logger.error("Global exception handler caught credentials failure", e);
        return ResponseEntity
                .status(HttpStatus.FORBIDDEN)
                .body("Authentication failed: Please check your access credentials");
    }
        
    // Additional exception handling methods can be added
    @ExceptionHandler(Exception.class)
    public ResponseEntity handleGenericException(Exception e) {
        logger.error("Unexpected error occurred", e);
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("An internal server error occurred");
    }
}

The @ControllerAdvice annotation enables this class to intercept specified exceptions thrown by all controllers throughout the application. @ExceptionHandler methods support flexible signatures and can receive parameters like HttpServletRequest and HttpServletResponse, providing extensive flexibility for exception handling.

Comparison and Selection Between Approaches

The ResponseEntity direct return approach is suitable for:

The @ControllerAdvice global approach is suitable for:

In practical projects, both approaches can be combined. For example, specific business exceptions can be handled directly with ResponseEntity in controllers, while system-level exceptions or those requiring unified handling can use @ControllerAdvice.

Advanced Features and Best Practices

Spring MVC provides rich exception handling mechanisms beyond the two main approaches, including:

@ResponseStatus Annotation: Custom exception classes can be directly annotated with @ResponseStatus to specify HTTP status codes when exceptions are thrown:

@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "Invalid credentials")
public class ChekingCredentialsFailedException extends RuntimeException {
    public ChekingCredentialsFailedException(String message) {
        super(message);
    }
}

Custom Error Message Formats: For RESTful APIs, unified error message formats can be defined:

public class ErrorResponse {
    private String timestamp;
    private int status;
    private String error;
    private String message;
    private String path;
    
    // Constructors, getters, and setters
}

Then return structured error information in exception handling methods:

@ExceptionHandler(ChekingCredentialsFailedException.class)
public ResponseEntity<ErrorResponse> handleException(ChekingCredentialsFailedException e, 
                                                     HttpServletRequest request) {
    ErrorResponse error = new ErrorResponse(
        Instant.now().toString(),
        HttpStatus.FORBIDDEN.value(),
        "Forbidden",
        e.getMessage(),
        request.getRequestURL().toString()
    );
    
    return ResponseEntity
            .status(HttpStatus.FORBIDDEN)
            .body(error);
}

Exception Handling Order: Spring MVC processes exceptions in a specific order:

  1. @ExceptionHandler methods within controllers
  2. @ExceptionHandler methods in @ControllerAdvice classes
  3. Exceptions annotated with @ResponseStatus
  4. Default exception resolvers

Performance Considerations and Security Recommendations

When implementing exception handling, consider the following performance and security factors:

Performance Optimization:

Security Considerations:

Conclusion

Spring MVC provides flexible and powerful exception handling mechanisms, allowing developers to choose appropriate solutions based on specific requirements. The ResponseEntity direct return approach suits simple, localized exception handling, while @ControllerAdvice global handling offers better code organization and reusability. In practical projects, combining both approaches typically achieves flexible yet unified exception handling strategies.

Regardless of the chosen approach, follow these best practices: use appropriate logging, return consistent error formats, consider security and performance impacts, and maintain clear, maintainable code. Proper exception handling design significantly enhances application robustness and user experience.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.