DEV Community

José Paulo Marinho
José Paulo Marinho

Posted on

Como fazer tratamento de exceções globalmente em Java Spring Boot

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

ou até mesmo a chamada à uma API:

Imagem que representa uma exceção em uma API

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:

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
}
Enter fullscreen mode Exit fullscreen mode

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:

public class BusinessException extends ExceptionCustomized{
    public BusinessException(String code, String message) {
        super(code, message);
    }
}
Enter fullscreen mode Exit fullscreen mode
public class TechnicalException extends ExceptionCustomized{
    public TechnicalException(String code, String message) {
        super(code, message);
    }
}
Enter fullscreen mode Exit fullscreen mode
public class NotFoundException extends ExceptionCustomized{
    public NotFoundException(String code, String message) {
        super(code, message);
    }
}
Enter fullscreen mode Exit fullscreen mode

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:

@ControllerAdvice
public class GlobalExceptionHandling extends ResponseEntityExceptionHandler {

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

    }
}
Enter fullscreen mode Exit fullscreen mode

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:

@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));
    }
Enter fullscreen mode Exit fullscreen mode

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:

public class Usuario {

    private String usuario;
    private Double altura;

    public Usuario(String usuario, Double altura) {
        this.usuario = usuario;
        this.altura = altura;
    }

    // Getters and Setters
}
Enter fullscreen mode Exit fullscreen mode

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

 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;
 }
Enter fullscreen mode Exit fullscreen mode

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

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

        throw new UsuarioNaoEncontradoException(nomeUsuario);
    }
Enter fullscreen mode Exit fullscreen mode

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:

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

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:

@GetMapping
    public ResponseEntity procurarUsuario(@RequestParam("usuario") String usuario) {
        return ResponseEntity.status(200).body(procurarUsuarioPeloNome(usuario));
    }
Enter fullscreen mode Exit fullscreen mode

Pronto, agora só executar.

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

Teste de execução API sem exceção

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

Teste de execução API com exceção

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.

Top comments (0)