Como fazer tratamento de exceções globalmente em Java Spring Boot
Published: 2023-03-30 13:21:58

Olá, Desenvolvedor, hoje estarei abordando um tema famoso:
**Global Exception Handler** ou **Tratamento de Exceções Globais.** 

## O que é uma Exceção?
Em Ciência da Computação, uma exceção é um evento que indica que algo inesperado ou não previsto ocorreu durante a execução de um programa. Quando uma exceção é lançada, isso indica que houve um problema em algum ponto do código e que algo precisa ser feito para lidar com essa situação de erro. O tratamento de exceções é uma técnica importante para garantir que o programa continue a funcionar de forma adequada e para fornecer feedback ao usuário ou ao sistema sobre o ocorrido. Em resumo, exceções são um mecanismo importante em programação para lidar com situações imprevistas e garantir a confiabilidade e a robustez do código.

Nesta publicação estarei trazendo formas de como tratar exceções globalmente em **Java** para uma API REST utilizando Spring, apesar de muitas linguagens de programação conterem suporte nativo para tratamento de exceções como Python, C++, PHP, .NET, Ruby e muitas outras.

## Por que tratar exceções?

Imagine que você está utilizando uma biblioteca de terceiros, e durante a execução do seu programa, vem o seguinte erro:

![Imagem que representa uma exceção no java](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jem97ng4oduh66709d8u.png)

ou até mesmo a chamada à uma API:

![Imagem que representa uma exceção em uma API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gxnejpswj1veoa7em52q.png)

**Que erro que deu aí? Será que esqueci de fazer algo ao chamar a API? Não passei alguma configuração na biblioteca?**

São dúvidas que são levantadas quando vem uma exceção que não é bem tratada. Você deixa o usuário confuso, perdido. Com o passar do tempo, seu programa, que agora é legado, vai ficando difícil procurar os erros que ocorrem.

Imagine o seguinte cenário também: quando seu projeto está em produção e seu sistema "loga" o problema deste jeito, dependendo do problema, tem que resolver rápido, e agora?

Tratar exceções é de suma importância para o desenvolvimento de software, pois permite que um programa lide melhor com os erros sem dar tiro no escuro. Ajuda a identificar problemas com mais agilidade e corrigi-los.

## Como funciona uma Exceção em **Java**?

Em Java, Exceções são representados por objetos que são criados e lançados **durante a execução de um programa**. Lembrando que não afeta nada no hardware, quando ocorre um erro no hardware ou no sistema operacional, geralmente é lançado um código de erro específico (um número diferente de 0) que é retornado ao sistema operacional ou à aplicação que está interagindo com o hardware. Esse código de erro pode ser usado para tomar ações apropriadas, como exibir uma mensagem de erro ao usuário, registrar o erro em um arquivo de log ou tentar corrigir o erro automaticamente.

Em Java, as exceções são usadas para sinalizar erros específicos que ocorrem durante a execução do programa, como tentar acessar uma posição inválida em um array ou chamar um método em um objeto nulo. Cada tipo de exceção tem uma classe correspondente em Java que é usada para criar e lançar a exceção.

Nós iremos fazer um pouco diferente das exceções já nativas, vamos criar as nossas.

## Iniciando o tratamento

Para isso iremos definir um modelo de negócio, no qual terá um classe customizada de exceção:

- `code`: código da nossa exceção, importante se formos querer documentar o código.
- `message`: mensagem que vai explicar nossa exceção.

Exemplo: `001` - `Token Expirado`

Ficando:

```java
public class ExceptionCustomized extends RuntimeException{
    private String code;
    private String message;

    public ExceptionCustomized(String code, String message) {
        super();
        this.code = code;
        this.message = message;
    }
    
    // Getters and Setters
}
```

Vamos definir mais algumas regras de negócio, que seriam os tipos das exceções, podendo ser:

-  `BusinessException`: exceção de regra de negócio;
-  `TechinalException`: exceção técnica.

Vou adicionar mais uma, opcional, que é a:

-  `NotFoundException`: indica que algo não foi encontrado

Cada uma delas terá uma classe que ***herdará*** da classe pai `ExceptionCustomized`, ficando:

```java
public class BusinessException extends ExceptionCustomized{
    public BusinessException(String code, String message) {
        super(code, message);
    }
}
```

```java
public class TechnicalException extends ExceptionCustomized{
    public TechnicalException(String code, String message) {
        super(code, message);
    }
}
```

```java
public class NotFoundException extends ExceptionCustomized{
    public NotFoundException(String code, String message) {
        super(code, message);
    }
}
```

Certo. Agora que definimos nossa regra de negócio, podemos partir para a brincadeira de verdade. Iremos criar outra classe que será responsável por todo o tratamento de exceções na aplicação, de qualquer camada, iremos anotar ela com a anotação `@ControllerAdvice`. 

Nesta classe vamos criar um método que vai retornar um objeto do tipo `ResponseEntity` chamado `exceptionHandling` que vai receber o objeto:
`Exception`, anotando ela com a anotação `ExceptionHandler` passando o nosso objeto de `ExceptionCustomized`, porque desta forma, especificamos qual método o Spring Framework precisa chamar ao lançar a exceção. Ficando assim:

```java
@ControllerAdvice
public class GlobalExceptionHandling extends ResponseEntityExceptionHandler {

    @ExceptionHandler(ExceptionCustomized.class)
    public final ResponseEntity<Object> expectionHandling(Exception ex) {

    }
}
```

Agora vamos tratar os tipos de exceção, como iremos retornar um objeto do tipo `ResponseEntity` precisamos dizer qual status code vai retornar, sendo para:

- BusinessException: 412
- NotFoundException: 404
- TechnicalException: 512

Ficando assim:

```java
@ExceptionHandler(ExceptionCustomized.class)
    public final ResponseEntity<Object> expectionHandling(Exception ex) {
        int statusCode = 0;

        if (ex instanceof BusinessException)
            statusCode = HttpStatus.UNPROCESSABLE_ENTITY.value();
        else if(ex instanceof NotFoundException)
            statusCode = HttpStatus.NOT_FOUND.value();
        else if(ex instanceof TechnicalException)
            statusCode = 512;
        else {
            statusCode = HttpStatus.INTERNAL_SERVER_ERROR.value();
            return new ResponseEntity(new ErrorResponse(String.valueOf(statusCode), "An unexpected error has occurred"), HttpStatus.valueOf(statusCode));
        }

        return new ResponseEntity<>(new ErrorResponse(((ExceptionCustomized) ex).getCode(), ex.getMessage()), HttpStatus.valueOf(statusCode));
    }
```

Criamos um objeto de ErrorResponse para respeitar o tipo de modelagem **DDD**, uma das abordagens de desenvolvimento de software. 

## Testando

Iremos criar uma classe de domínio usuário para posteriormente simular uma base de dados de usuário:
```java
public class Usuario {

    private String usuario;
    private Double altura;

    public Usuario(String usuario, Double altura) {
        this.usuario = usuario;
        this.altura = altura;
    }
    
    // Getters and Setters
}
```

Iremos criar um método que inicializará uma lista de Usuários para simular um banco de dados.

```java
 public List<Usuario> inicializarLista() {
    List<Usuario> listaUsuarios = new ArrayList<>();
    listaUsuarios.add(new Usuario("José", 1.78));
    listaUsuarios.add(new Usuario("Paulo", 1.79));
    return listaUsuarios;
 }
```

Agora vamos criar um método para procurar pelo usuário na lista de usuários:

```java
public Usuario procurarUsuarioPeloNome(String nomeUsuario) {
        var listaUsuarios = this.inicializarLista();
        for (Usuario usuario : listaUsuarios) {
            if (nomeUsuario.equals(usuario.getUsuario()))
                return usuario;
        }

        throw new UsuarioNaoEncontradoException(nomeUsuario);
    }
```
Notaram algo diferente? Existe uma linha conténdo o algoritmo:
`throw new UsuarioNaoEncontradoException(nomeUsuario);`
Se você perceber, no Java nativamente não existe essa exceção, então vamos criar:

```java
public class UsuarioNaoEncontradoException extends NotFoundException {
    public UsuarioNaoEncontradoException(String usuario) {
        super("001", String.format("Usuário %s não encontrado", usuario));
    }
}
```

Legal, né? Todas as exceções que lançarmos do tipo `UsuarioNaoEncontradoException` irá conter o código **001** e uma breve descrição de qual usuário não foi encontrado.

Vamos adicionar o seguinte código a uma controller:

```java
@GetMapping
    public ResponseEntity procurarUsuario(@RequestParam("usuario") String usuario) {
        return ResponseEntity.status(200).body(procurarUsuarioPeloNome(usuario));
    }
```

## Pronto, agora só executar.

Uma execução passando um usuário que existe:

![Teste de execução API sem exceção](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b7c3u2y30zp4db45dy3n.png)

Uma execução passando um usuário que não existe:

![Teste de execução API com exceção](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fyfr76rt66w5ch2a3u3o.png)

Essa foi uma das muitas formas de **Tratamento de exceções Globais**, espero que tenham gostado!

O código fonte pode ser encontrado em:
**https://github.com/jusebandtec/global-exception-handler**

Até mais.