Em Java A Palavra-chave Throws É Usada Para
O que é a cláusula throws em Java
O throws aparece na assinatura do método quando aquele método pode lançar uma exceção checked e decide não tratá-la ali mesmo. Em vez de capturar dentro do corpo, você joga a responsabilidade para quem chamou. É um mecanismo simples, mas todo mundo acaba errando na prática. Basicamente, ela declara no contrato do método que determinadas exceções podem ser jogadas para cima na pilha de execução. Sem isso, o compilador não deixa seu código compilar se você invoca um método que levanta checked exception e não há try-catch no caminho. A sintaxe é direta:
public void meuMetodo() throws IOException, SQLException Você lista as exceções no final da assinatura, separadas por vírgula. Se o método precisar declarar mais de uma, tudo fica na mesma cláusula throws. O compilador impõe que todas as checked exceptions declaradas sejam tratadas ou propagadas. O ponto que os tutoriais não mostram claro é que throws não cria a exceção. Ele apenas informa que alguma chamada interna pode lançar aquela exceção. A real criação acontece na linha que invoca o método problemático, não na assinatura.
Eu li muita gente achando que throws é um try-catch disfarçado. Não é. É apenas uma transferência de responsabilidade.
Como funciona na prática
Vou dar um exemplo real que eu vejo o tempo todo em code review: Aqui o throws IOException é obrigatório porque FileReader e BufferedReader lançam essa exceção checked. Se você não colocar throws nem try-catch, o código nem compila. Simples assim. O problema é que esse código tem um vazamento. Se readLine() falhar, o close() nunca roda. Isso acontece com frequência em código legado que eu encontro. A solução correta seria usar try-with-resources:
public void processarArquivo(String caminho) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(caminho))) {
String linha = reader.readLine();
System.out.println(linha);
}
}
Com try-with-resources, o Java fecha o recurso automaticamente, mas o throws ainda é necessário. A exceção continua sendo propagada, só que o recurso é fechado de forma garantida. Isso faz diferença quando você trabalha com arquivos grandes em produção. Outro detalhe importante: se um método sobrescrito declara menos exceções no throws do que o método da superclasse, tudo bem. Mas se declarar mais, o compilador reclama. A regra é que subclasses podem reduzir a lista de exceções, nunca expandir. Isso me custou uma hora de debugging numa implementação de DAO onde eu adicionei uma exceção checked nova no override sem perceber. O compilador deveria ter avisado, mas o IDE não sinalizou na hora porque o projeto tava configurado com regras de análise estática bem permissivas. A correção foi simples, mas o tempo gasto pra identificar o problema não foi.
Pegadinhas comuns
A primeira pegadinha é achar que throws serve para unchecked exceptions também. Tecnicamente você pode declarar RuntimeException e Error na cláusula, mas não precisa. O compilador não exige. A convenção é usar throws apenas para checked exceptions. A segunda pegadinha é esquecer que cada chamada em cadeia carrega a responsabilidade. Se seu método A chama método B que tem throws IOException, e A não trata nem declara, o código não compila. A exceção precisa ser tratada ou declarada em todos os níveis da pilha até chegar no main ou no container de execução. A terceira pegadinha é mais sutil: exceções multiplicadas. Alguns desenvolvedores colocam throws Exception como preguiça. Isso anula toda a utilidade do mecanismo porque quem for chamar o método não sabe o que esperar. O código fica difícil de manter e o tratamento de erros vira um exercício de adivinhação.

Na minha experiência, o cenário mais problemático envolve métodos que precisam lidar com múltiplos tipos de exceção checked de fontes diferentes. Um método que abre conexão JDBC e lê um arquivo vai declarar throws SQLException, IOException. Quem chamar vai precisar lidar com ambos ou declarar novamente. Isso gera uma propagação em cascata que às vezes chega até a camada de apresentação, o que não é ideal. A solução que eu uso nesses casos é criar uma exceção personalizada que encapsula as exceções originais. Assim a assinatura do método fica mais limpa e o tratamento é centralizado:
public class DataAccessException extends Exception {
public DataAccessException(Exception cause) {
super(cause);
}
}
Depois você converte as checked exceptions originais em DataAccessException dentro do método e declara throws DataAccessException. A camada de cima trata apenas um tipo. Isso reduz a complexidade de propagação e evita que cada invocação precise de múltiplos blocos catch. A pergunta mais frequente é se deve tratar a exceção localmente ou propagar. A resposta depende do contexto. Se você consegue recuperar do erro ali mesmo, use try-catch. Se não tem como fazer nada útil localmente, use throws e deixe quem chamou decidir. Um exemplo claro: num método que valida dados de entrada, você pode capturar NumberFormatException e retornar um erro de validação específico. Nesse caso o catch é mais adequado porque você tem informação suficiente para tratar.
Já num serviço que lê um arquivo de configuração, talvez não faça sentido tentar recuperar se o arquivo não existir. Nesse caso throws é mais honesto porque comunica claramente que a operação pode falhar e quem invoca precisa estar preparado. O erro mais comum que eu vejo é tratar tudo com try-catch genérico e só fazer log. Isso esconde o problema e transforma exceções checkeds em ruído silencioso. O código compila, passa nos testes, e na produção alguém descobre que o sistema ignora erros de forma consistente.

Mitos sobre throws
Existe a ideia de que declarar throws em todos os métodos é sinal de código ruim. Não é necessariamente verdade. Declarações corretas de exceções checked são parte do design do contrato do método. O problema é declarar exceções que nunca acontecem ou usar throws Exception para evitar pensar no tratamento. Outro mito é que checked exceptions são obsoletas. Elas existem desde o Java 1.0 e continuam sendo úteis em APIs que precisam garantir tratamento obrigatório. Frameworks como JDBC ainda usam muito esse padrão. O debate sobre checked versus unchecked existe, mas não significa que throws seja uma construção defeituosa. O que faz o código ficar ruim não é o uso de throws, é o mau uso. Declarar exceções demais, tratar mal, ou propagar até camadas que não têm contexto para decidir o que fazer. Isso sim gera fragilidade.
Cenários onde throws falha
O mecanismo throws não funciona bem em contextos assíncronos. Se você usa CompletableFuture, streams parallel, ou callbacks, a cláusula throws não se aplica da mesma forma porque o método que lança a exceção não é o mesmo que executa. Nesse caso você precisa usar handlers específicos como handle(), whenComplete(), ou onErrorResume() Outro cenário problemático é com frameworks de injeção de dependência que usam proxies. O Spring, por exemplo, pode envolver seu bean em um proxy CGLIB ou JDK dynamic proxy, e exceções declaradas no throws podem ser capturadas pelo framework antes de chegar ao código original. Isso acontece especialmente com @Transactional, onde o framework gerencia a transação e converte exceções em RuntimeExceptions de controle de transação. Se você está trabalhando com Spring e precisa propagar uma checked exception através de um proxy, o caminho mais seguro é capturar dentro do método e lançar uma RuntimeException envoltória, ou configurar o ponto de corte para não ser proxy. Ambas as abordagens têm trade-offs que valem a pena entender antes de implementar.
Resumo prático
O throws serve para declarar no contrato do método que ele pode lançar determinadas exceções checked. Não trata, não cria, apenas comunica. Use quando não há como recuperar localmente. Não use para unchecked exceptions. Prefira exceções específicas a Exception genérico. E lembre-se que try-with-resources resolve a maior parte dos problemas de recursos que precisam ser fechados, mesmo quando o throws continua necessário. A palavra-chave throws é uma ferramenta básica do Java, mas o jeito como as pessoas usam revela muito sobre a maturidade do código. Exceções mal declaradas ou mal tratadas são uma das causas mais comuns de bugs difíceis de rastrear em produção.

Galeria
-
Java: Difference in throws and throw