Alura > Cursos de Programação > Cursos de Java > Conteúdos de Java > Primeiras aulas do curso Java exceções: aprenda a criar, lançar e controlar exceções

Java exceções: aprenda a criar, lançar e controlar exceções

Conhecendo exceções - Apresentação

Olá, me chamo Vinícius Lousada, faço parte da Escola de Programação e DevOps, e por questões de acessibilidade, vou me autodescrever.

Audiodescrição: Vinicius se autodescreve como um homem de pele em tom pardo, possui cabelo e barba na cor preta, está usando fones de ouvido, vestindo camiseta preta e sentado em um quarto com iluminação azul.

Durante este curso de Exceções em Java, vamos aprender muita coisa interessante!

O que é uma Exception?

Entenderemos, desde o início, o que é uma exceção, porque elas ocorrem, como podemos tratá-las e até mesmo como criar nossas próprias exceções.

Vamos aprender tudo isso a partir de um projeto prático, que é a adopet, um projeto destinado à adoção de cães e gatos. Bem interessante, não é? E essa API está no Spring Boot. Também entenderemos de que forma o Spring Boot vai nos auxiliar no tratamento dessas exceções.

Para este curso, é necessário que você tenha concluído a formação de Java: orientação a objetos e também Spring Boot API aqui na Alura.

Venha comigo começar essa aventura!!

Conhecendo exceções - Recebendo uma exceção

Então, vamos começar o curso?

No meu visor, está aparecendo a IDE que vamos utilizar, o IntelliJ. Também teremos uma atividade para vocês baixarem o material deste curso de exceções. Já estou com o projeto aberto na IDE do IntelliJ. Vamos navegar um pouco para descobrirmos o que há na API.

Visão Geral da API Adopet

Estamos trabalhando no contexto da Adopet, uma empresa especializada em solicitações e aprovações de adoções de pets. Temos uma API no SpringBoot e estamos com essas pastas: src, main, java e vamos clicar no pacote da adopet.api.

Lembrando que o foco do curso não é ensinar o que é o SpringBoot e como usá-lo desde o início. Existem outros cursos específicos de para isso.

Temos os pacotes de controller, dto, model, repository e service. Vamos clicar no controller. Abriram-se três novos arquivos: AdocaoController, PetController e TutorController. O AdocaoController é como o coração da Adopet API, pois ela é especializada em adoções de pets. Então, vamos dar um duplo clique e abrir sua visualização.

Então, abrimos o controller e estamos mapeando-o para os verbos HTTP. Possuímos métodos @GetMapping, responsáveis por buscar alguns dados na API, por exemplo, buscar todas as adoções, buscar uma única adoção, solicitar, aprovar e reprovar.

Para consultar o pacote adopet.api, basta acessar o Github do curso

Conforme avançarmos, entenderemos como as exceções aparecerão no nosso código.

Durante este curso, como estamos trabalhando com uma API no Spring Boot que precisa armazenar dados, como os de adoção, vamos utilizar um banco de dados MySQL. Na pasta main, acessando resources, encontraremos um arquivo chamado application.properties. Vamos dar um duplo clique em application.properties, minimizar os projetos do IntelliJ e dar zoom.

spring.datasource.url=jdbc:mysql://localhost/adopet_api?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=123

Pela URL é possível conferir que o MySQL já está instalado na minha máquina.

Há um material disponível no Para Saber Mais da aula que você pode consultar para baixar e instalar o MySQL na sua máquina.

Temos o username e o password. Coloquei a configuração da minha máquina, ou seja, o meu usuário é root e a senha é 123. Mas pode ser que na sua máquina isso seja diferente. Então, não deixe de configurar de acordo com a sua máquina.

Início da Aplicação e Testes com Postman

Está tudo correto. Passamos pelo AdocaoController.java e vamos iniciar nossa API. No canto superior direito, encontraremos um botão de "Play" escrito "Run 'ApiApplication'". Ao apertá-lo, aparecerá uma mensagem pedindo para habilitarmos o Lombok. Vamos clicar e habilitar. No canto inferior do visor, é possível conferir que a aplicação está iniciando.

A aplicação foi iniciada e agora precisamos testá-la. Se tivéssemos um front-end, teríamos um formulário de inputs para preencher e fazer requisições para a API. Mas como não temos, vamos utilizar o Postman.

Também disponibilizaremos um material complementar para você instalar e configurar o Postman no seu computador. Ele pedirá que você tenha uma conta. É possível logar com sua conta do Google ou criar uma. Quando abrir o Postman, você encontrará um botão chamado "Import". Peço que importe uma coleção de requisições que preparei para você não ter que escrever manualmente.

Você clicará em "Import" e depois em "Files". O próximo passo é voltar ao projeto e minimizar a parte inferior do console. Voltando no diretório de arquivos, você perceberá que na raiz do projeto que disponibilizei, existe um arquivo chamado Adopet.Postman_Collection.json. Basta abrir esse arquivo no Postman.

Voltando ao Postman, localizaremos uma pasta chamada "Adopet". Dentro dela, haverá outras pastas, como a de Pet, Tutor, Adoção, e algumas requisições que preparamos, com métodos POST, GET e PUT. Por exemplo, temos o POST Cadastrar Tutor. Ao selecioná-lo, abriremos uma janela na parte direita.

Nesta janela, selecionaremos "Body", como o arquivo já foi disponibilizado, não será necessário preparar o JSON que enviaremos para a API. No corpo, estamos enviando um arquivo "raw", um "corpo cru", do tipo JSON, passando a chave "nome" e também o "email".

"nome": "Vinicius Louzada", 
"email": "email@example.com"

Podemos disparar uma requisição. No canto direito, há um botão azul chamado "Send". Vamos apertá-lo e, na parte inferior, notaremos que ele retornou um "200 OK", e informou que conseguimos cadastrar o tutor.

Vale lembrar que na nossa API do Spring Boot, temos um método responsável por cadastrar o tutor. Para saber se tudo deu certo, podemos ir na rota de buscar todos os tutores. Está com a URL localhost:8080/tutor, enviando pelo verbo GET. Clicaremos em "Send" e, pronto, ele cadastrou:

{
  {
      "id": 1,
      "nome": "Vinicius Louzada",
        "email": "email@example.com"
     }
}

Também precisamos cadastrar um pet, então, vamos na rota do pet, POST Cadastrar Pet e selecionaremos o "Body". Um ponto importante nesta parte, é: quando temos um pet, precisamos cadastrar uma imagem também. Mas como enviar uma imagem através do Postman? Se estivéssemos em um ambiente front-end, teríamos um botão de seleção e arquivos.

Temos a parte do "Body", e clicaremos na opção "form-data". Enviaremos duas chaves,:

1 - Imagem (selecionando o tipo de arquivo como "file"). Em "Value", apertaremos "Select Files", abriremos o projeto e selecionaremos a imagem "gato.jpg". O "content-Type" precisamos mandar do "multipart/form-data", assim informaremos ao navegador que estamos enviando arquivos de multimidia.

2 - Dados. Em "Value", teremos os dados do pet. Já no "content-Type", teremos "application/json".

Vamos voltar ao IntelliJ. Na "src > main > java > Adopet API" abriremos o PetController. Minimizaremos o diretório de arquivos do IntelliJ. Na nossa rota, temos o método cadastrar, que espera receber uma chave dados e outra imagem, que é exatamente o que estamos passando no Postman.

// Código omitido. 

 @PostMapping
    @Transactional
    public ResponseEntity<String> cadastrar(@RequestPart @Valid CadastroPetDTO dados,
                                            @RequestParam MultipartFile imagem){
        try{
            service.cadastrar(dados, imagem);
        }catch (IOException ex){
            ResponseEntity.badRequest().body(ex.getMessage());
        }
        return ResponseEntity.ok().build();
    }
}

Voltando ao Postman, clicaremos em "Send" no canto superior direito. Ele retornou "200 OK", logo, o pet foi cadastrado. Para confirmar, podemos clicar em "Buscar todos os Pets" e, em seguida, no botão "Send".

Ele cadastrou o pet no nosso banco de dados, com nome, idade, tipo e se foi adotado. Em relação à imagem, ele criou um novo nome com a extensão e foi enviada para a API, que a gravará em um diretório raiz. Vamos ao IntelliJ entender melhor este contexto. Temos a pasta main, e uma outra chamada resource, que contém a pasta storage, de armazenamento. Nela, está cadastrada a imagem que enviamos, a fotografia de um gato.

Até aqui, cadastramos o tutor e o pet na nossa API, no nosso banco de dados. Com essas informações, podemos solicitar uma adoção e isso já está preparado. Na pasta de "adoção", vamos clicar em "Body". Podemos solicitar a adoção do pet 1 e do tutor 1, que foram os primeiros registros no nosso banco de dados. Precisamos também passar um motivo. Dispararemos essa requisição clicando em "Send". Ele retorna uma mensagem: "adoção solicitada com sucesso".

{
   "idPet": 1,
     "idTutor": 1,
     "motivo": "Um motivo qualquer"
}

Adoção solicitada com sucesso!

Agora, para testar, precisamos aprovar essa adoção. Antes disso, vamos buscar todas as adoções. Clicaremos no botão "Send" para enviar. Ele retornou um JSON:

[
  {
      "id": 1,
       "tutor": 1,
       "pet": 1,
       "motivo": "Um motivo qualquer",
       "status": "AGUARDANDO_AVALIAÇÃO", 
       "justificativa": null
     }
]

Então, alguém da Adopet avaliará se a justificativa é plausível e se a pessoa pode adotar o pet. Vamos testar e aprovar essa adoção. Clicaremos em PUT Aprovar Adoção e acessaremos "Body". Aprovaremos a adoção 1, que foi a primeira solicitação.

{
    "idAdocao": 1
 }

Clicaremos em "Send". O retorno é "200 OK". Se voltarmos a GET Buscar todas as adoções e dispararmos novamente, o status aparecerá como aprovado.

[
  {
      "id": 1,
       "tutor": 1,
       "pet": 1,
       "motivo": "Um motivo qualquer",
       "status": "APROVADO", 
       "justificativa": null
     }
]

Tratamento de Exceções

Mas imagine um caso onde alguém tenta aprovar uma adoção que não existe. O que acontecerá? Vamos fazer um teste. Na PUT Aprovar Adoção, temos apenas uma solicitação, já aprovada, a solicitação 1. Colocaremos o número 2, que ainda não existe e testar clicando em "Send".

{
    "idAdocao": 2
 }

O Postman retornou um erro interno da API, com status "500". Há um rastro de erro e ele menciona "EntityNotFoundException". Significa que ele não encontrou a adoção.

Voltando ao IntelliJ, no AdocaoController, temos o método aprovar e uma service com um método de aprovar. Vamos abri-lo. Ele faz uma busca pelo id que passamos no banco de dados e tenta criar uma entidade. Depois, tenta marcá-la como aprovada. Mas, como não encontrou, retorna um "EntityNotFoundException".

// Código omitido. 

 public void aprovar(AprovarAdocaoDTO dto){
        Adocao adocao = adocaoRepository.getReferenceById(dto.idAdocao());
        adocao.marcarComoAprovada();
        adocao.getPet().marcarComoAdotado();
    }

    public void reprovar(ReprovarAdocaoDTO dto){
        Adocao adocao = adocaoRepository.getReferenceById(dto.idAdocao());
        adocao.marcarComoReprovada(dto.justificativa());
    }
}

Portanto, temos um erro acontecendo em tempo de execução, pois a API não conseguiu lidar com a situação e estourou uma exceção. Isso não é bom no desenvolvimento, principalmente em produção, pois retornamos todo o rastro da API, o que podemos considerar como uma falha de segurança. Na sequência, entenderemos melhor sobre essas exceções.

Conhecendo exceções - Entendendo a pilha de execução

No último vídeo, conseguimos fazer a população do nosso banco de dados. Conhecemos um pouco sobre a API (Interface de Programação de Aplicações) da Adopet, cadastramos pets na nossa aplicação e cadastramos tutores, que são pessoas que gostariam de fazer solicitações de adoções. Também conhecemos a parte de adoção, realizando os cadastros e as solicitações de adoção.

Quando tentamos aprovar uma adoção que não existe, uma exception foi retornada, junto a um rastro gigantesco com classes e métodos.

Entendendo o que é uma Stack

Antes de prosseguirmos nos nossos estudos sobre exceções, vamos explorar um conceito importante, de Stack. Para isso, voltaremos ao nosso projeto no IntelliJ e abriremos o diretório de pastas e acessaremos a pasta Java. Nela, existe uma classe chamada PilhaDeExecucao.java. Vamos dar duplo clique e entrar.

Essa classe, PilhaDeExecucao.java, é uma classe genérica criada para entendermos o que acontece quando iniciamos uma aplicação em Java.

public class PilhaDeExecucao {
    public static void metodo1() {
        System.out.println("[Inicio] - metodo1");

        metodo2();

        System.out.println("[Fim] - metodo1");
    }

    public static void metodo2() {
        System.out.println("[Inicio] - metodo2");

        Usuario usuario = new Usuario(nome: "Vinicius");

            System.out.println(usuario.nome);


        System.out.println("[Fim] - metodo2");
    }

    public static void main(String[] args) {
        System.out.println("[Inicio] - main");

        try {
            metodo1();
        }catch (NullPointerException ex){
            System.out.println("Usuário não encontrado");
        }

        System.out.println("[Fim] - main");
    }
}

class Usuario {
    public String nome;
    public Usuario(String nome) {
        this.nome = nome;
    }
}

Mais abaixo no código, temos o método main, que é por onde começa a nossa aplicação Java. Estamos printando com System.out.println. Por exemplo, quando formos iniciar essa nossa aplicação, que será só o arquivo PilhaDeExecucao.java, ele vai iniciar na main, depois vai chamar o metodo1() e não vai executar o fim da main, porque entrará no metodo1(). O método está na parte de cima do nosso código.

public class PilhaDeExecucao {
    public static void metodo1() {
        System.out.println("[Inicio] - metodo1");

        metodo2();

        System.out.println("[Fim] - metodo1");
    }

// Código omitido. 

Dentro do metodo1(), vamos printar o início metodo1(). Em seguida, perceba que há outro método, que é o metodo2(). Então ele vai entrar no metodo2(), printar o início do metodo2(), e fazer uma busca, por exemplo, uma instância de Usuario, classe genérica que está no final.

// Código omitido. 

class Usuario {
    public String nome;
    public Usuario(String nome) {
        this.nome = nome;
    }
}

Temos só um nome, e ele está recebendo esse nome no construtor.

Voltando para o metodo2(), ele vai pegar esse nome e vai printar o nome de Vinícius. Depois finalizará o metodo2(), volta para o metodo1(), que chamou o metodo2(), e printa o fim do metodo1(). Por fim, volta para a main e finaliza a main.

No canto superior direito, vamos parar a API apertando o botão de "stop". Na lateral direita, ao lado do método main, temos o botão de "Play", vamos clicar nele e rodar PilhaDeExecucao.main(). Ele printou o que está acontecendo:

[Inicio] - main
[Início] - metodo1
[Início] - metodo2
Vinícius 
[Fim] - metodo2
[Fim] - metodo1
[Fim]  - main

Portanto, temos:

Para entendermos, a partir de um recurso visual, o que está acontecendo. Estou com um slide aberto e no canto superior esquerdo, está escrito "Console". Quando iniciamos a nossa aplicação, ele vai printar o início do método main: "[Inicio] - main".

Na parte direita do nosso visual, há uma espécie de livro que sobre uma mesa. Então, ele inicia o método main, que por sua vez, vai executar o que precisa ser executado, por exemplo, alguma regra de negócio ou validação.

A main, neste caso, vai chamar o metodo1(). Então, o metodo1() fica sobre a main, ou seja, no topo dessa execução. É como se fosse uma pilha de livros. Seguindo, o metodo1() também vai executar o que ele precisa.

Só que o metodo1(), ele chama o metodo2(). Sendo assim, gora quem está no comando, ou seja, que está sendo executado, é o metodo2(). E o metodo2() vai executar toda a regra de negócio que ele precisa. Nesse caso, seria criar uma instância: printar o nome "Vinícius". Feito isso, ele vai finalizar o metodo2() e volta para o metodo1(). Quando o metodo1() termina tudo o que precisava fazer, a main continua. Neste caso, apenas finaliza.

Tudo isso que acompanhamos acontecendo é uma stack (pilha de execução), muito comum em Java e também no mundo da programação. Quando vamos iniciar uma aplicação, ele vai organizar a nossa execução no formato de uma pilha.

Entendendo a exceção

Agora vamos criar um cenário em que ele vai ao banco de dados e não encontra "Vinicius". Então, vamos passar null.

// Código omitido. 

    }

    public static void metodo2() {
        System.out.println("[Inicio] - metodo2");

        Usuario usuario = null;

            System.out.println(usuario.nome);


        System.out.println("[Fim] - metodo2");
    }

Então, o objeto usuario vai receber null. O que vai acontecer quando tentarmos printar o nome desse usuário? Vamos executar e testar. O método main foi iniciado e Inicio também. O metodo1() vai fazer o Inicio do metodo1() e depois vai entrar no metodo2.

No metodo2(), ele printou o Inicio do metodo2 Só que, na hora de mostrar o nome desse usuário, o que aconteceu? Ele retornou o NullPointerException. Essa exceção é muito comum no mundo Java.

Ele não conseguiu encontrar o atributo nome, porque o usuário é nulo. Na parte inferior, ele mostra uma pilha de execução. Podemos notar que ele passou pela main. Em cima da main, temos o metodo1(). Acima dele, o metodo2(). Então, no metodo2 aconteceu a exceção.

Portanto, o nosso código não conseguiu lidar com o caso de erro que aconteceu em tempo de execução e a nossa aplicação parou. E isso não é o que queremos. Aprenderemos a tratar esse erro na sequência.

Sobre o curso Java exceções: aprenda a criar, lançar e controlar exceções

O curso Java exceções: aprenda a criar, lançar e controlar exceções possui 128 minutos de vídeos, em um total de 49 atividades. Gostou? Conheça nossos outros cursos de Java em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Java acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas