Gostaria de falar neste artigo sobre tratamento de erros e exceções, eles ocorrem quando algo imprevisto acontece e podem ter origem por erros de lógica ou acesso a recursos que talvez não estejam disponíveis. Por exemplo, ao tentar fazer consulta a um banco de dados que não está disponível ou ainda tentar abrir um arquivo que não existe.
É importante dizer que existe várias maneiras de tratar exceções e que de maneira alguma estou dizendo que a forma que mostrarei aqui é a melhor, porém, escolhi fazer desta maneira por achar que será mais fácil de ser entendida para quem está começando no assunto.
Para facilitar e explicar de maneira prática vou usar a API com o CRUD de produtos que foi o projeto que criei no artigo anterior. Primeiramente criei um novo método no projeto que é o de buscar um produto por id.
Na classe ProductService criei o método para buscar um produto passando o id do mesmo, como pode ou não ser encontrado a gente usa a classe Optional do java.util do tipo Product e depois usamos o método “findById” do ProductRepository.
Agora implementamos o método na classe ‘ProductController’ que recebe um @PathVariable na url da requisição e buscar o produto através do método implementado em nossa camada de serviço.
Ao testar no Postman com o método GET selecionado e passando o id 4 na url temos o produto que pertence aquele id retornado como resposta a requisição.
E se agora a gente tentar buscar um id que não existe no banco de dados? Vamos ver o que acontece..
Como você pode ver, ele retornou para gente uma exceção com algumas informações sobre o erro. Primeiramente vamos analisar o status code que é o código de resposta da requisição e o mesmo veio como status 500 internal server error.
Quando o servidor retorna um código de erro (HTTP) 500, indica que encontrou uma condição inesperada e que o impediu de atender à solicitação ou seja, nesse caso não é legal enviar esse código de status pois não é um erro do servidor e sim da solicitação do usuário. Para isso temos o status 404 – NOT FOUND que indica que o servidor não conseguiu encontrar o recurso solicitado. Vamos mudar isso! Obs. Para saber mais sobre códigos de status http veja https://developer.mozilla.org/pt-BR/docs/Web/HTTP/Status
Para tratar essa exceção de maneira personalizada vamos começar criando uma pasta chamada exceptionhandler e a classe MessageExceptionHandler que terá 3 atributos como mostra a imagem acima. Teremos um construtor com parâmetros e getter & setters.
Agora criei a classe ProductNotFoundException que estende a classe RunTimeException, que é a mais indicada para erros que ocorrem normalmente por dados inválidos, como um usuário buscar por um produto ou usuário que não existe.
Por fim criei a classe ProductControllerAdvice que será executada sempre que uma exceção for lançada. Essa classe deve tem a @ControllerAdvice que imforma o pacote dos controllers da aplicação. Ela possui um método para cada tipo de exceção que o sistema pode gerar. No exemplo a seguir, vamos capturar a exceção ProductNotFoundException no método productNotFound. O atributo basePackages da anotação @ControllerAdvice indica que ela deve verificar as exceções retornadas em todos os controllers. O valor HttpStatus.NOT_FOUND passado na anotação @ResponseStatus indica que deve ser retornado o erro 404 como status da resposta. A exceção ProductNotFoundException.class na anotação @ExceptionHandler indica que esse método deve capturar esse tipo de exceções. Finalmente, a anotação @ResponseBody define que o retorno desse método será retornado no corpo da resposta.
Agora no método de busca por id, caso o produto não seja encontrado a gente lança o nosso erro ProductNotFoundException.
Testando novamente a busca por id e passando um id inválido veja que agora nosso erro voltou de maneira muito mais ‘agradável’, com o código correto 404 de produto não encontrado e com a mensagem que passamos que facilita para o usuário do que se trata o erro.
E o mais legal é que ao ter aproveitado esse método para validar a busca por id nos outros métodos (PUT e DELETE) a gente também ganha de brinde esse mesmo tratamento.
Teste de requisição deletar passando um id inválido.
Teste de requisição atualizar passando um id inválido.
Para ficar ainda mais completo nosso artigo vamos tratar erros de validações e para isso vamos adicionar a dependência de validação do spring-boot no nosso arquivo pom.xml.
E ganhamos varias anotações e dentre elas a anotação que iremos utilizar que é a @NotNull que basicamente diz que ao cadastrar um novo produto aquele atributo não poderá ser nulo.
Mas só o @NotNull não basta, para validar ainda precisamos da anotação @Valid que é passada no como parâmetro no nosso método para criar um novo produto.
E se tentarmos agora cadastrar um novo produto deixando de passar algum dos atributos teremos uma exceção do tipo MethodArgumentNotValidException e essa mensagem de erro gigante. O status está correto o 400 que diz que o servidor não pode encontrar o recurso solicitado. Vamos tratar esse erro para que ele traga uma mensagem mais amigável.
Como nesse caso podem ser mais de um campo que faltou ser enviado, retornaremos uma mensagem indicando quais campos possuem valores inválidos. E para isso criei o método argumentsNotValid e usaremos uma lista da classe FieldError que mostrará quais campos devem ser preenchidos. Usei também o a classe StringBuider para criar nossa mensagem personalizada. No final estanciei a classe MessageExceptionHandler que criamos anteriormente e passei os argumentos. Note que dessa vez vamos usar o status 400 que é o BAD_REQUEST.
Por fim, ao tentar cadastrar um novo produto sem passar o campo nome teremos nossa mensagem de erro personalizada indicando qual campo precisa ser preenchido. Bem legal não é?
E um novo teste deixando de passar mais campos ao cadastrar o produto ele retorna nosso erro mostrando quais campos devem ser preenchidos.