Exceções Personalizadas no Java Spring
- #Spring Framework
- #Java
Vamos supor um cenário em que um endpoint deve executar um método findById para apresentar uma entidade. Uma requisição do tipo GET é feita para um número de Id inexistente. Por padrão, essa requisição irá retornar um erro “Internal Server Error” com status 500.
Essa não é uma resposta que queremos para as requisições de nossa aplicação web. Isso pode dificultar o trabalho de desenvolvimento front-end, além de ser uma apresentação nada elegante aos usuários da aplicação.
Nesse contexto, é possível criarmos um tratamento personalizado para a exceção, em poucos passos, e atendendo às boas práticas:
Passo 1 – Podemos começar atuando na camada de Service, afinal, um tratamento personalizado é, também, uma regra de negócio. Vamos criar um pacote exceptions dentro do pacote service. Dentro dele, vamos criar uma classe java que vamos chamar de EntityNotFoundException, a qual deverá estender a classe RuntimeException. No construtor da classe, vamos receber como argumento uma mensagem do tipo String, e repassar esse argumento para a classe pai:
public class EntityNotFoundException extends RuntimeException {
public EntityNotFoundException(String msg){
super(msg);
}
}
Passo 2 – Agora já podemos atuar na camada de Controller. Vamos criar um pacote exceptions dentro do pacote controller. Dentro dele vamos criar duas classes. Vamos começar com a classe que nomearemos StandardError, contendo alguns atributos, um construtor vazio e os respectivos getters e setters:
public class StandardError {
private Instant timestamp;
private Integer status;
private String error;
private String message;
private String path;
public StandardError(){
}
public Instant getTimestamp() {
return timestamp;
}
public void setTimestamp(Instant timestamp) {
this.timestamp = timestamp;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
}
Passo 3 – Depois de criada a classe StandardError, vamos criar também a classe ControllerExceptionHandler dentro do mesmo pacote controller.exceptions. Essa classe deve receber a anotação do spring @ControllerAdvice. Com essa anotação, a classe passará a monitorar a classe controller da entidade:
@ControllerAdvice
public class ResourceExceptionHandler {
}
Passo 3.1 – Agora que a classe ControllerExceptionHandler já está monitorando o controller da entidade, vamos criar nela uma função de tratamento de exceção. Essa função será do tipo ResponseEntity com atribuição daquele tipo StandardError (que criamos no passo 3). Vamos chamar essa função de entityNotFound e passar como argumentos uma EntityNotFoundException e uma HttpServletRequest:
@ControllerAdvice
public class ControllerExceptionHandler {
public ResponseEntity<StandardError> entityNotFound(EntityNotFoundException e, HttpServletRequest request) {
}
}
3.2 – Dentro da função, vamos instanciar um StardardError e setar os valores para cada um dos respectivos atributos. Ao final retornaremos uma ResponseEntity com os métodos que possibilitam a exibição do status e da mensagem de erro (lembrando que o HttpStatus.NOT_FOUND retorna o famoso status 404):
@ControllerAdvice
public class ControllerExceptionHandler {
public ResponseEntity<StandardError> entityNotFound(EntityNotFoundException e, HttpServletRequest request) {
StandardError err = new StandardError();
err.setTimestamp(Instant.now());
err.setStatus(HttpStatus.NOT_FOUND.value());
err.setError("Resource not found");
err.setMessage(e.getMessage());
err.setPath(request.getRequestURI());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(err);
}
}
3.3 – Agora precisamos anotar essa função com a anotação @Exceptionhandler tendo como argumento a nossa classe EntityNotFoundException. Dessa forma, “avisamos” ao Spring que esta função é um tratamento de exceção. Vai ficar assim:
@ControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<StandardError> entityNotFound(EntityNotFoundException e, HttpServletRequest request) {
StandardError err = new StandardError();
err.setTimestamp(Instant.now());
err.setStatus(HttpStatus.NOT_FOUND.value());
err.setError("Resource not found");
err.setMessage(e.getMessage());
err.setPath(request.getRequestURI());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(err);
}
}
4 – Finalmente, vamos voltar para camada de serviço e incluir o tratamento de exceção no método findById da EntityService. Nesse ponto, repare que o método findById chamado provem da classe JpaRepository e retorna um Optional<T>. Por isso, criamos uma variável obj e posteriormente usamos o método .orElseThrow para converter o objeto em entidade ou, alternativamente, capturar a exceção com a mensagem que desejarmos:
@Transactional(ReadOnly = true)
public EntityDTO findById(Long id) {
Optional<Entity> obj = entityRepository.findById(id);
Entity entity = obj.orElseThrow(() -> new EntityNotFoundException("Entity not found"));
return new EntityDTO(entity);
}
É isso pessoal, espero ter contribuído. Assim como a maioria de vocês aqui, também estou aprendendo e aproveito para dizer que tentar passar conhecimento adiante é uma excelente forma de assimilar o que estudamos.
Abraços!