Capítulo 11: Tratamento Robusto de Exceções, Hierarquia Throwable, Try-With-Resources e Exceções Customizadas
Especialização em Sistemas com Java 21 LTS • Módulo de Herança e Contratos
🗺️ Mapa Conceitual do Tópico
flowchart TD
A["java.lang.Throwable"] --> B["java.lang.Error (Crítico / Fatal)"]
A --> C["java.lang.Exception (Recuperável)"]
B --> D["OutOfMemoryError / StackOverflowError"]
C --> E["Checked Exceptions (javac exige throws / try-catch)"]
C --> F["java.lang.RuntimeException (Unchecked)"]
E --> G["IOException / SQLException / ParseException"]
F --> H["NullPointerException / IllegalArgumentException / RegraNegocioException"]
subgraph MANIPULACAO["Mecanismos Robustos em Java 21"]
I["try-with-resources (AutoCloseable)"]
J["Multi-catch (catch IOException | SQLException e)"]
K["Exception Chaining (initCause / getCause)"]
end
C --> MANIPULACAO
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#ffebee,stroke:#f44336,stroke-width:2px
style E fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style F fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style I fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
💥 1. A Hierarquia Throwable da JVM
Na plataforma Java, qualquer entidade que possa ser lançada (throw) ou capturada (catch) deve herdar direta ou indiretamente de java.lang.Throwable. A árvore de tipos divide-se em duas ramificações fundamentais:
1. java.lang.Error
Representa condições de falha severa na infraestrutura do ambiente de execução (JVM ou hardware do servidor).
- Exemplos:
OutOfMemoryError,StackOverflowError,InternalError. - Diretriz: Aplicações comuns nunca devem tentar capturar
Error, pois a integridade do runtime e a consistência da memória estão comprometidas.
2. java.lang.Exception
Representa condições excepcionais e anômalas das quais o programa pode razoavelmente se recuperar ou aplicar contingência.
⚖️ 2. Checked vs Unchecked Exceptions
O compilador javac impõe regras estritas de verificação de tipos nas exceções:
Checked Exceptions (extends Exception)
- Verificação em Compilação: O compilador exige obrigatoriamente que o método declare o lançamento (
throws MinhaExcecao) ou faça a captura interna através de um blocotry-catch. - Cenário Típico: Falhas externas imprevisíveis sobre as quais o código local não tem controle absoluto (falha de rede, arquivo não encontrado, indisponibilidade temporária de banco).
Unchecked Exceptions (extends RuntimeException)
- Isenção de Verificação: Não exigem declaração explícita na assinatura do método.
- Cenário Típico: Erros de programação e violação de contratos e invariantes da API (
NullPointerException,IndexOutOfBoundsException,IllegalArgumentException,IllegalStateException). - Design Moderno: Em arquiteturas corporativas contemporâneas (Spring Boot, Quarkus), a quase totalidade das exceções de regra de negócio herda de
RuntimeExceptionpara evitar poluição de assinaturas em camadas de serviço.
🛡️ 3. O Mecanismo Moderno try-with-resources e AutoCloseable
Antes do Java 7, o fechamento de recursos como arquivos, conexões JDBC e sockets exigia blocos finally manuais propensos a vazamento de ponteiros ou mascaramento de exceções originais.
O Padrão try-with-resources:
Qualquer classe que implemente a interface funcional java.lang.AutoCloseable pode ser gerenciada automaticamente pelo compilador:
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(SQL)) {
// Processamento do recurso
} catch (SQLException e) {
logger.error("Falha na consulta SQL", e);
} // conn e ps são encerrados automaticamente aqui, mesmo em caso de erro!
Exceções Suprimidas (Suppressed Exceptions):
Se uma exceção for lançada no corpo do bloco try e o método .close() automático do recurso também lançar uma exceção secundária, a JVM preserva a exceção do bloco principal e anexa a segunda como suprimida, acessível via e.getSuppressed().
🎯 4. Encadeamento de Exceções (Exception Chaining) e Exceções de Domínio
Nunca capture uma exceção de baixo nível (como SQLException) para simplesmente imprimir no console e continuar a execução. Em arquiteturas limpas, converta a falha técnica em uma exceção de domínio corporativa sem perder a raiz do problema:
public class SaldoInsuficienteException extends RuntimeException {
private final BigDecimal saldoDisponivel;
private final BigDecimal valorTentado;
public SaldoInsuficienteException(BigDecimal saldo, BigDecimal valor) {
super(String.format("Saldo insuficiente: R$ %.2f para saque de R$ %.2f", saldo, valor));
this.saldoDisponivel = saldo;
this.valorTentado = valor;
}
// Construtor com encadeamento de causa técnica
public SaldoInsuficienteException(String mensagem, Throwable causa) {
super(mensagem, causa);
this.saldoDisponivel = BigDecimal.ZERO;
this.valorTentado = BigDecimal.ZERO;
}
}
📊 5. Matriz Comparativa: Classificação de Falhas
| Característica | Error |
Checked Exception | Unchecked RuntimeException |
|---|---|---|---|
| Classe Mãe | java.lang.Throwable |
java.lang.Exception |
java.lang.RuntimeException |
Checagem pelo javac |
Não | Sim (Obrigatória) | Não |
Cláusula throws |
Opcional | Obrigatória se não capturada | Opcional (recomendada em Javadoc) |
| Recuperabilidade | Não recuperável | Recuperável via contingência | Recuperável / Correção de código |
| Exemplo Real | OutOfMemoryError |
FileNotFoundException |
IllegalArgumentException |
💻 6. Código de Demonstração Corporativo (Java 21 LTS)
package com.empresa.excecoes;
import java.math.BigDecimal;
/**
* Sistema financeiro com tratamento de exceções de domínio e encadeamento de causas.
*/
public class SistemaBancarioResiliente {
public static class ContaInativaException extends RuntimeException {
public ContaInativaException(String contaId) {
super("A conta bancária [" + contaId + "] está cancelada ou bloqueada judicialmente.");
}
}
public static class SaldoInsuficienteException extends RuntimeException {
public SaldoInsuficienteException(BigDecimal saldoAtual, BigDecimal saqueDesejado) {
super(String.format("Tentativa de débito de R$ %.2f excede o saldo disponível de R$ %.2f.", saqueDesejado, saldoAtual));
}
}
public static class ServicoOperacaoFinanceira implements AutoCloseable {
private final String canalAuditado;
private boolean aberto = true;
public ServicoOperacaoFinanceira(String canal) {
this.canalAuditado = canal;
System.out.printf("🔌 [CANAL] Conexão segura estabelecida via [%s]%n", canalAuditado);
}
public void debitar(String conta, BigDecimal saldo, BigDecimal valor, boolean ativa) {
if (!aberto) throw new IllegalStateException("Canal de operação já encerrado.");
if (!ativa) throw new ContaInativaException(conta);
if (valor.compareTo(saldo) > 0) throw new SaldoInsuficienteException(saldo, valor);
System.out.printf(" ✅ Débito de R$ %.2f concluído com sucesso na conta %s!%n", valor, conta);
}
@Override
public void close() {
this.aberto = false;
System.out.printf("🔒 [CANAL] Conexão [%s] encerrada com sucesso (Flush de logs).%n", canalAuditado);
}
}
public static void main(String[] args) {
System.out.println("================================================================================");
System.out.println(" PROCESSAMENTO TRANSACIONAL COM TRY-WITH-RESOURCES (JAVA 21) ");
System.out.println("================================================================================");
// 1. Operação com Try-with-resources garantindo o fechamento da sessão
try (ServicoOperacaoFinanceira servico = new ServicoOperacaoFinanceira("TERMINAL-AGENCIA-01")) {
servico.debitar("CC-9901", new BigDecimal("1000.00"), new BigDecimal("350.00"), true);
servico.debitar("CC-9902", new BigDecimal("200.00"), new BigDecimal("800.00"), true); // Dispara erro de saldo
} catch (SaldoInsuficienteException e) {
System.err.printf("⚠️ Interceptada Regra de Negócio: %s%n", e.getMessage());
} catch (ContaInativaException e) {
System.err.printf("🚨 Interceptada Restrição Cadastral: %s%n", e.getMessage());
}
System.out.println("================================================================================");
}
}