Tratamento de Exceções Global: Como padronizar erros na sua API com @ControllerAdvice
Sua API funciona perfeitamente no caminho feliz, mas o que acontece quando o usuário envia um dado errado ou busca um ID que não existe? Retornar uma stack trace gigante de Java no navegador é um pesadelo de segurança e uma dor de cabeça para o Front-end. Neste post, vamos aprender a criar um tratamento de exceções elegante e centralizado no Spring Boot usando @ControllerAdvice e @ExceptionHandler, construindo respostas de erro padronizadas e profissionais.
O pesadelo do Erro 500 e o Front-end quebrado
Quando estamos focados em construir novas funcionalidades ou até mesmo correndo contra o tempo para estruturar e validar um projeto, é muito comum focar apenas no "caminho feliz" da aplicação. O problema aparece quando o usuário faz algo inesperado.
Se você buscar no banco de dados um produto pelo ID 999 e ele não existir, a sua aplicação provavelmente vai disparar uma exceção. O comportamento padrão do Spring Boot é capturar isso e devolver ao cliente um genérico Erro 500 (Internal Server Error), muitas vezes acompanhado por dezenas de linhas de uma Stack Trace do Java exposta em formato JSON.
Por que isso é terrível?
1.Segurança: Você está vazando informações da estrutura interna do seu código, versões de pacotes e nomes de classes.
2.Experiência do Desenvolvedor (DX): O desenvolvedor Front-end (web ou mobile) que consome sua API não consegue ler uma Stack Trace para exibir um aviso amigável ao usuário. Ele precisa de um JSON claro, com um código de status HTTP correto (como o 404 Not Found) e uma mensagem descritiva.
Para resolver isso de forma elegante, o ecossistema Spring nos entrega duas ferramentas: o @ControllerAdvice e o @ExceptionHandler.
O que é o @ControllerAdvice?
Imagine que o @ControllerAdvice é uma espécie de "interceptor" global da sua aplicação.
Em vez de você ter que colocar blocos de try/catch dentro de absolutamente todos os métodos dos seus Controllers (o que fere gravemente o Princípio DRY que já discutimos aqui no blog), você cria uma única classe anotada com @ControllerAdvice. O Spring ficará atento: sempre que qualquer Controller disparar uma exceção, ela será redirecionada para essa classe antes de chegar ao usuário.
Implementando o Tratamento Padronizado na Prática
Para que o Front-end consiga tratar todos os erros da mesma forma, precisamos criar um "molde" padrão para as nossas mensagens de erro.
Passo 1: O Molde da Resposta (Usando Java Records)
Aproveitando os recursos do Java 17 e 21, vamos criar um Record simples para representar a nossa estrutura de erro. Todo erro que a nossa API devolver terá esse exato formato:
package com.alexsousadev.api.exception;
import java.time.LocalDateTime;
// Um DTO imutável e direto ao ponto
public record ErroPadrao(
LocalDateTime timestamp,
Integer status,
String error,
String path
) {}
Passo 2: Criando uma Exceção Customizada
Em vez de usar exceções genéricas do Java, é uma excelente prática criar exceções de negócio. Vamos criar uma para quando um recurso não for encontrado no banco de dados.
package com.alexsousadev.api.exception;
public class RecursoNaoEncontradoException extends RuntimeException {
public RecursoNaoEncontradoException(String mensagem) {
super(mensagem);
}
}
Passo 3: A Interceptação Global
Agora, criamos o nosso manipulador central. Esta classe vai "escutar" a aplicação inteira.
package com.alexsousadev.api.exception;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import java.time.LocalDateTime;
@ControllerAdvice
public class ManipuladorExcecoesGlobal {
// Avisamos ao Spring: "Se alguém lançar essa exceção específica, execute este método"
@ExceptionHandler(RecursoNaoEncontradoException.class)
public ResponseEntity<ErroPadrao> entityNotFound(RecursoNaoEncontradoException e, HttpServletRequest request) {
HttpStatus status = HttpStatus.NOT_FOUND; // Retorna 404 ao invés de 500
ErroPadrao erro = new ErroPadrao(
LocalDateTime.now(),
status.value(),
e.getMessage(),
request.getRequestURI()
);
return ResponseEntity.status(status).body(erro);
}
// Você pode ter múltiplos handlers aqui para capturar validações de formulário, erros de banco, etc.
@ExceptionHandler(Exception.class)
public ResponseEntity<ErroPadrao> erroGenerico(Exception e, HttpServletRequest request) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
ErroPadrao erro = new ErroPadrao(
LocalDateTime.now(),
status.value(),
"Ocorreu um erro interno inesperado. Contate o suporte.",
request.getRequestURI()
);
return ResponseEntity.status(status).body(erro);
}
}
O Resultado Final
Agora, se a sua classe Service não encontrar um produto e lançar um throw new RecursoNaoEncontradoException("Produto ID 999 não encontrado");, o front-end receberá um JSON perfeitamente formatado e previsível:
JSON
{
"timestamp": "2026-05-20T15:30:00.123",
"status": 404,
"error": "Produto ID 999 não encontrado",
"path": "/produtos/999"
}
Conclusão
Delegar o tratamento de exceções para o @ControllerAdvice é o que separa um código amador de uma API profissional preparada para escalar. O seu código de negócios (Controllers e Services) fica extremamente limpo, focado apenas no sucesso da operação, enquanto uma camada especializada cuida de formatar as falhas.
Além de garantir a segurança dos dados do seu servidor, os desenvolvedores Front-end vão agradecer muito pela previsibilidade que a sua API passará a ter.