Professional Error Handling & Logging in Spring Boot

boot.cloud
Boot to Cloud
Published on Apr, 27 2026 3 min read 0 comments
image

In Week 5, we secured our Spring Boot APIs using JWT authentication and role-based access.
Now, in Week 6, we’ll focus on handling errors gracefully and implementing professional logging — two crucial aspects of production-ready applications.

By the end of this article, your application will catch errors centrally, provide clean API responses, and have a robust logging system for debugging and monitoring.

Why Error Handling & Logging Matter

❌ Without proper error handling:

  • APIs return messy stack traces
  • Debugging is difficult
  • User experience suffers

❌ Without logging:

  • Hard to monitor production issues
  • No insights into user behavior
  • Difficult to troubleshoot crashes

✅ Proper handling and logging provide:

  • Centralized error management
  • Consistent API responses
  • Easy debugging and monitoring
  • Professional, maintainable code

Step 1: Global Exception Handling with @ControllerAdvice

Spring Boot allows centralized exception handling using @ControllerAdvice:

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ApiError> handleResourceNotFound(ResourceNotFoundException ex) {
        ApiError error = new ApiError(HttpStatus.NOT_FOUND, ex.getMessage());
        return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ApiError> handleValidationException(MethodArgumentNotValidException ex) {
        String message = ex.getBindingResult()
                           .getFieldErrors()
                           .stream()
                           .map(DefaultMessageSourceResolvable::getDefaultMessage)
                           .collect(Collectors.joining(", "));
        ApiError error = new ApiError(HttpStatus.BAD_REQUEST, message);
        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ApiError> handleGenericException(Exception ex) {
        ApiError error = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, "Something went wrong");
        return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

ApiError Class

public class ApiError {
    private int status;
    private String message;
    private LocalDateTime timestamp;

    public ApiError(HttpStatus status, String message) {
        this.status = status.value();
        this.message = message;
        this.timestamp = LocalDateTime.now();
    }

    // Getters & Setters
}

✅ Benefits:

  • Consistent error response structure
  • Easy for frontend to parse
  • Centralized handling reduces boilerplate

Step 2: Custom Exceptions

Create domain-specific exceptions:

public class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException(String message) {
        super(message);
    }
}

Usage in Service:

User user = userRepository.findById(id)
           .orElseThrow(() -> new ResourceNotFoundException("User not found with ID " + id));

Step 3: Logging Setup

Spring Boot uses SLF4J + Logback by default.
You can customize logging in application.yml:

logging:
  level:
    root: INFO
    com.example.demo: DEBUG
  file:
    name: logs/app.log
  • root → default logging level
  • package-specific → fine-grained logs
  • file.name → log to file for production

Step 4: Logging in Code

@RestController
@RequestMapping("/api/users")
public class UserController {

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

    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        logger.info("Fetching user with ID: {}", id);
        User user = userService.getUserById(id)
                     .orElseThrow(() -> new ResourceNotFoundException("User not found"));
        logger.debug("User details: {}", user);
        return ResponseEntity.ok(user);
    }
}

Logging Best Practices:

  • Use info for general events
  • Use debug for detailed dev info
  • Use error for exceptions

Step 5: Structured Logging (Optional for Production)

Use JSON logging for modern applications:

<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
    <providers>
        <timestamp>
            <timeZone>UTC</timeZone>
        </timestamp>
        <loggerName />
        <level />
        <threadName />
        <message />
    </providers>
</encoder>
  • Easier for ELK / Grafana / Kibana dashboards
  • Perfect for microservices and cloud deployments

Step 6: Combining Error Handling + Logging

Centralized exception handler + logging:

@ExceptionHandler(Exception.class)
public ResponseEntity<ApiError> handleGenericException(Exception ex) {
    logger.error("Unexpected error occurred: ", ex);
    ApiError error = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, "Something went wrong");
    return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
  • Every exception is logged
  • Users get clean messages

Step 7: Validation Error Logging Example

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiError> handleValidationException(MethodArgumentNotValidException ex) {
    String message = ex.getBindingResult()
                       .getFieldErrors()
                       .stream()
                       .map(err -> err.getField() + ": " + err.getDefaultMessage())
                       .collect(Collectors.joining(", "));
    logger.warn("Validation failed: {}", message);
    ApiError error = new ApiError(HttpStatus.BAD_REQUEST, message);
    return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
}

✅ Developers immediately see which fields failed without crashing the app.

Week 6 Recap

✅ Centralized exception handling with @ControllerAdvice
✅ Custom exceptions for domain logic
✅ Logging with SLF4J / Logback
✅ Logging levels: info, debug, warn, error
✅ Structured logging for production
✅ Combining exception handling + logging for maintainable APIs

What’s Next? (Week 7)

👉 Spring Boot Testing: Unit, Integration & Performance

We’ll cover:

  • JUnit 5 + Mockito
  • Integration testing with Spring Boot
  • TestContainers for database
  • API testing
  • Performance tips

📌 Action Items

✔ Implement @ControllerAdvice
✔ Add custom exceptions for your entities
✔ Add logging to all controllers and services
✔ Configure logging levels in application.yml
✔ Test logging + error responses in Postman

0 Comments