O que é uma exceção e por que ela existe
Uma exceção é um evento que interrompe o fluxo normal do programa quando algo inesperado acontece (por exemplo: entrada inválida, arquivo inexistente, divisão por zero). Em Java, exceções são objetos (subclasses de Throwable) que carregam informações sobre o problema e permitem que você decida entre: recuperar (continuar com um plano alternativo), falhar com mensagem clara (erro significativo) ou propagar (deixar camadas superiores decidirem).
Uma boa estratégia de exceções busca: mensagens úteis, stack trace preservado, encapsulamento (não vazar detalhes internos) e fluxos de recuperação quando fizer sentido.
try/catch/finally: controle do fluxo em caso de falha
Estrutura básica
try { // código que pode falhar} catch (AlgumaException e) { // tratamento/recuperação} finally { // sempre executa (com ou sem exceção)}try: envolve o trecho que pode lançar exceções.catch: captura exceções específicas para tratar (ou transformar) o erro.finally: executa independentemente de sucesso/erro; útil para liberar recursos quando não se usa try-with-resources.
Ordem e especificidade dos catches
Coloque exceções mais específicas antes das genéricas. Caso contrário, o compilador reclama porque o catch genérico “engole” os específicos.
try { // ...} catch (NumberFormatException e) { // específico} catch (RuntimeException e) { // mais genérico}Multi-catch
Quando o tratamento é igual para mais de um tipo:
try { // ...} catch (java.io.IOException | java.nio.file.InvalidPathException e) { System.err.println("Falha ao acessar caminho/arquivo: " + e.getMessage());}Exceções checadas vs não checadas (checked vs unchecked)
Checked exceptions
São exceções que o compilador obriga você a tratar (try/catch) ou declarar (throws). Em geral, representam condições que podem ocorrer em tempo de execução e que o chamador pode razoavelmente tentar recuperar (ex.: I/O).
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Exemplo comum: IOException.
Unchecked exceptions
São subclasses de RuntimeException. O compilador não exige tratamento. Normalmente indicam erro de programação, pré-condições violadas ou estados inválidos (ex.: NullPointerException, IllegalArgumentException, ArithmeticException).
Regra prática
- Use checked quando o chamador pode agir (tentar outro arquivo, repetir operação, pedir novo input).
- Use unchecked para validação de argumentos e invariantes (o código não deveria ter chamado daquele jeito).
Stack trace: como ler e como preservar
O stack trace mostra a cadeia de chamadas que levou ao erro, do ponto onde a exceção foi lançada até o ponto onde foi tratada (ou até o programa terminar). Ele é essencial para depuração.
Exemplo de saída (simplificada)
Exception in thread "main" java.lang.NumberFormatException: For input string: "abc" at java.lang.Integer.parseInt(Integer.java:...) at app.Calculadora.parseEntrada(Calculadora.java:12) at app.Main.main(Main.java:7)Não perca a causa original
Um erro comum é capturar e lançar outra exceção sem encadear a causa, perdendo o stack trace original. Prefira:
catch (java.io.IOException e) { throw new RuntimeException("Falha ao ler o arquivo de configuração", e);}O segundo argumento (e) preserva a causa e facilita muito o diagnóstico.
Mensagens úteis: o que escrever (e o que evitar)
Checklist de uma boa mensagem
- O que falhou (ação): “Não foi possível carregar o arquivo”.
- Onde/qual recurso: caminho, id, parâmetro (sem expor segredos).
- Por quê (quando disponível): mensagem da exceção original ou contexto.
- O que o usuário pode fazer: “Verifique se o arquivo existe e se há permissão”.
Evite
- Mensagens genéricas: “Erro ocorreu”.
- Vazar detalhes sensíveis (tokens, senhas, conteúdo de arquivo).
- Engolir exceções sem registrar/propagar:
catch (Exception e) {}.
Encapsulamento de exceções (camadas e responsabilidades)
Em aplicações com camadas (ex.: leitura de arquivo, regras de negócio, interface), é comum encapsular exceções técnicas em exceções mais significativas para a camada acima.
Exemplo: transformar IOException em erro de domínio
public class ConfigService { public String carregarConfig(String caminho) { try { return java.nio.file.Files.readString(java.nio.file.Path.of(caminho)); } catch (java.io.IOException e) { // Encapsula detalhe técnico em uma exceção mais clara para o chamador throw new ConfiguracaoException("Não foi possível ler a configuração em: " + caminho, e); } }}Assim, quem chama ConfigService lida com ConfiguracaoException (mais semântica), mas o stack trace original continua disponível via getCause().
try-with-resources: fechamento automático em I/O
Recursos como streams, readers e writers precisam ser fechados. O try-with-resources garante fechamento automático mesmo se ocorrer exceção, reduzindo vazamentos e simplificando código. Ele funciona com tipos que implementam AutoCloseable.
Passo a passo: ler um arquivo com tratamento adequado
Objetivo: ler a primeira linha de um arquivo e lidar com “arquivo inexistente” e outros problemas de I/O.
- Defina o caminho do arquivo.
- Abra o recurso dentro do
try (...). - Leia o conteúdo.
- Trate exceções específicas com mensagens úteis.
import java.io.BufferedReader;import java.io.IOException;import java.nio.file.Files;import java.nio.file.NoSuchFileException;import java.nio.file.Path;public class LeitorPrimeiraLinha { public static String lerPrimeiraLinha(String caminho) { Path path = Path.of(caminho); try (BufferedReader br = Files.newBufferedReader(path)) { return br.readLine(); } catch (NoSuchFileException e) { throw new IllegalStateException("Arquivo não encontrado: " + caminho + ". Verifique o caminho.", e); } catch (IOException e) { throw new IllegalStateException("Falha de I/O ao ler: " + caminho + ". Verifique permissões.", e); } }}Note que o recurso (BufferedReader) é fechado automaticamente. As exceções são encapsuladas com mensagens orientadas ao que fazer.
Detalhe importante: exceções no close
Se ocorrer exceção durante o fechamento, o Java pode anexá-la como suppressed à exceção principal. Isso é útil para diagnóstico e aparece no stack trace.
Criação de exceções customizadas
Exceções customizadas ajudam a expressar erros do seu domínio (ex.: “PagamentoRecusado”, “ConfiguracaoInvalida”). Em geral, crie uma classe com construtores que aceitem mensagem e causa.
Exemplo: exceção de configuração
public class ConfiguracaoException extends RuntimeException { public ConfiguracaoException(String message) { super(message); } public ConfiguracaoException(String message, Throwable cause) { super(message, cause); }}Quando usar checked vs unchecked em customizadas
- Unchecked (extends
RuntimeException): quando é erro de estado/ambiente que você não quer obrigar o chamador a tratar em todo lugar, mas ainda quer semântica clara. - Checked (extends
Exception): quando o chamador deve decidir explicitamente como recuperar (ex.: operação que pode falhar e tem alternativas).
Validação de argumentos com IllegalArgumentException
Quando um método recebe parâmetros inválidos, a exceção mais comum é IllegalArgumentException. Ela é unchecked e comunica que o chamador violou uma pré-condição.
Passo a passo: validar entrada antes de processar
- Liste as pré-condições (ex.: não nulo, não vazio, faixa numérica).
- Valide no início do método (fail fast).
- Escreva mensagem indicando o parâmetro e a regra.
public class Validador { public static int parseIdPositivo(String texto) { if (texto == null) { throw new IllegalArgumentException("Parâmetro 'texto' não pode ser null"); } String t = texto.trim(); if (t.isEmpty()) { throw new IllegalArgumentException("Parâmetro 'texto' não pode ser vazio"); } try { int id = Integer.parseInt(t); if (id <= 0) { throw new IllegalArgumentException("Id deve ser > 0. Valor recebido: " + id); } return id; } catch (NumberFormatException e) { throw new IllegalArgumentException("Id inválido: '" + texto + "'. Informe um número inteiro.", e); } }}Repare no encadeamento da causa ao transformar NumberFormatException em IllegalArgumentException com mensagem mais orientada ao usuário.
Cenários práticos e fluxos de recuperação
1) Entrada inválida (número digitado errado)
Cenário: o usuário informa “abc” quando o sistema espera um inteiro.
Estratégia: validar e pedir novamente (recuperação) ou falhar com mensagem clara (em APIs).
Exemplo: loop de recuperação no console
import java.util.Scanner;public class EntradaComRecuperacao { public static int lerInteiro(Scanner sc, String rotulo) { while (true) { System.out.print(rotulo); String s = sc.nextLine(); try { return Integer.parseInt(s.trim()); } catch (NumberFormatException e) { System.out.println("Valor inválido: '" + s + "'. Digite um número inteiro."); } } }}Aqui a exceção é usada para controlar a recuperação: ao falhar, o programa informa e tenta novamente.
2) Arquivo inexistente
Cenário: caminho errado ou arquivo removido.
Estratégia: tratar NoSuchFileException separadamente para mensagem específica; oferecer alternativa (ex.: usar padrão) quando aplicável.
import java.io.IOException;import java.nio.file.Files;import java.nio.file.NoSuchFileException;import java.nio.file.Path;public class Carregador { public static String carregarOuPadrao(String caminho, String padrao) { try { return Files.readString(Path.of(caminho)); } catch (NoSuchFileException e) { // Recuperação: usa valor padrão return padrao; } catch (IOException e) { // Não recuperável aqui: propaga com contexto throw new IllegalStateException("Falha ao ler arquivo: " + caminho, e); } }}3) Divisão por zero
Cenário: denominador igual a zero.
Estratégia: evitar depender de ArithmeticException para regra de negócio; valide antes e retorne erro significativo.
public class Matematica { public static double dividir(double numerador, double denominador) { if (denominador == 0.0) { throw new IllegalArgumentException("Denominador não pode ser zero"); } return numerador / denominador; }}Se você estiver trabalhando com int, a divisão por zero lança ArithmeticException. Mesmo assim, preferir validação explícita costuma gerar mensagens melhores e regras mais claras.
4) Escrevendo mensagens e definindo o ponto certo de tratamento
Decida onde tratar com base em quem consegue agir:
- Camada de interface (console/UI/API): geralmente trata e mostra mensagem amigável.
- Camada de serviço/regra: pode validar, encapsular e propagar.
- Camada de infraestrutura (I/O): captura exceções técnicas e adiciona contexto (caminho, operação).
Exemplo: fluxo completo com encapsulamento e mensagem amigável
import java.util.Scanner;public class App { public static void main(String[] args) { try (Scanner sc = new Scanner(System.in)) { System.out.print("Informe o caminho do arquivo: "); String caminho = sc.nextLine(); String conteudo = new ConfigService().carregarConfig(caminho); System.out.println("Config carregada. Tamanho: " + conteudo.length()); } catch (ConfiguracaoException e) { // Mensagem amigável para o usuário System.out.println(e.getMessage()); // Em um app real, você também registraria o stack trace em log // e.printStackTrace(); } }}Neste fluxo, a interface mostra uma mensagem clara (sem detalhes técnicos excessivos), enquanto a exceção mantém a causa original para diagnóstico.
Tabela rápida: escolhas comuns
| Situação | Exceção típica | Abordagem recomendada |
|---|---|---|
| Argumento inválido (null, vazio, fora de faixa) | IllegalArgumentException | Validar no início do método e falhar com mensagem específica |
| Entrada textual não é número | NumberFormatException | Recuperar pedindo novamente (UI) ou encapsular em IllegalArgumentException (API) |
| Arquivo não existe | NoSuchFileException | Mensagem específica; opcionalmente recuperar com padrão |
| Falha geral de I/O | IOException | Try-with-resources; encapsular com contexto e preservar causa |
| Regra de negócio violada | Exceção customizada | Nome semântico, mensagem clara, causa quando houver |