09 - Spring Handlers de Exceção e Controller Advice
Forma Tradicional de Lidar com Exceções
Em uma aplicação Spring básica, as exceções são frequentemente tratadas diretamente dentro dos métodos do controller usando blocos try-catch.
Exemplo:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public User findById(@PathVariable Long id) {
try {
return userService.findById(id);
} catch (UserNotFoundException ex) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND,
ex.getMessage()
);
}
}
}
Problemas com essa abordagem
- Código repetido em múltiplos controllers.
- Difícil de manter.
- A lógica de negócio se mistura com o tratamento de erros.
- Respostas de erro inconsistentes.
- Viola o princípio da Separação de Preocupações (Separation of Concerns).
À medida que a aplicação cresce, essa abordagem se torna difícil de gerenciar.
Exception Handler
Spring oferece a anotação @ExceptionHandler para centralizar o tratamento de exceções dentro de um controller.
Exemplo:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public User findById(@PathVariable Long id) {
return userService.findById(id);
}
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFound(
UserNotFoundException ex) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ex.getMessage());
}
}
Como funciona
Quando uma UserNotFoundException é lançada:
- Spring procura por um
@ExceptionHandler. - O método handler correspondente é executado.
- Uma resposta HTTP personalizada é retornada.
Vantagens
- Remove blocos
try-catchdos métodos do controller. - Código do controller mais limpo.
- Tratamento de exceções centralizado dentro de um controller.
- Manutenção mais fácil.
Limitação
O exception handler funciona apenas para o controller onde foi declarado.
Se você tiver múltiplos controllers, pode acabar duplicando o mesmo código de tratamento de exceções.
Necessidade de Controller Advice e Implementação
Para evitar a duplicação de exception handlers em vários controllers, Spring oferece a anotação @ControllerAdvice.
@ControllerAdvice atua como um exception handler global para todos os controllers na aplicação.
Por que usar Controller Advice?
Sem @ControllerAdvice:
UserController
└── @ExceptionHandler(UserNotFoundException)
ProductController
└── @ExceptionHandler(UserNotFoundException)
OrderController
└── @ExceptionHandler(UserNotFoundException)
O mesmo código é repetido várias vezes.
Com @ControllerAdvice:
GlobalExceptionHandler
└── @ExceptionHandler(UserNotFoundException)
UserController
ProductController
OrderController
Um único handler atende a toda a aplicação.
Exemplo de Implementação
Exceção Personalizada
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(String message) {
super(message);
}
}
Global Exception Handler
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<String> handleUserNotFound(
UserNotFoundException ex) {
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(ex.getMessage());
}
}
Controller
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public User findById(@PathVariable Long id) {
return userService.findById(id);
}
}
Quando a exceção é lançada:
throw new UserNotFoundException("User not found");
Spring automaticamente direciona a exceção para o handler global.
Resposta:
HTTP/1.1 404 Not Found
User not found
Retornando uma Resposta de Erro Personalizada
Em vez de retornar uma string simples, é comum retornar uma resposta JSON estruturada.
DTO de Erro
public record ErrorResponse(
String message,
int status,
LocalDateTime timestamp) {
}
Handler
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorResponse> handleUserNotFound(
UserNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
ex.getMessage(),
HttpStatus.NOT_FOUND.value(),
LocalDateTime.now()
);
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body(error);
}
}
Resposta:
{
"message": "User not found",
"status": 404,
"timestamp": "2026-07-27T20:30:00"
}
@ControllerAdvice vs @RestControllerAdvice
@ControllerAdvice
Usado para aplicações MVC e pode retornar:
- Views
- Objetos Model
- ResponseEntity
@ControllerAdvice
public class GlobalExceptionHandler {
}
@RestControllerAdvice
Combinação de:
@ControllerAdvice
@ResponseBody
Recomendado para APIs REST porque as respostas são automaticamente serializadas para JSON.
@RestControllerAdvice
public class GlobalExceptionHandler {
}
Resumo
| Abordagem | Scope | Recommended |
|---|---|---|
| Try-Catch no Controller | Método único | ❌ Não |
@ExceptionHandler | Controller único | ⚠️ Pequenas aplicações |
@ControllerAdvice | Múltiplos controllers | ✅ Sim |
@RestControllerAdvice | APIs REST | ✅ Melhor Prática |
Boas Práticas
- Crie exceções personalizadas para cenários de negócio.
- Lance exceções da camada de serviço.
- Lide com exceções globalmente usando
@RestControllerAdvice. - Retorne respostas de erro JSON padronizadas.
- Mantenha os controllers focados no processamento de requisições e fluxo de negócio.
- Evite duplicar a lógica de tratamento de exceções em vários controllers.
- Inclua informações úteis nas respostas de erro, como:
- Mensagem de erro
- Código de status HTTP
- Timestamp
- Caminho da requisição (opcional)
- Código de erro (opcional)
Para APIs REST modernas com Spring Boot, @RestControllerAdvice combinado com @ExceptionHandler é a abordagem recomendada para um tratamento de exceções centralizado e de fácil manutenção.