W3docs

Exceções Checked vs. Unchecked em Java

A diferença entre exceções checked e unchecked (runtime) em Java e quando usar cada uma.

Java divide suas exceções em dois grupos, e o compilador os trata de forma diferente. As exceções checked precisam ser capturadas ou declaradas; o compilador recusa compilar código que as ignora. As exceções unchecked não têm esse requisito — o compilador permite que se propaguem silenciosamente e somente o runtime as captura. Essa divisão é uma das decisões mais características e mais debatidas do Java, e entendê-la molda como você escreve código robusto.

Qual é qual

Tudo que é Throwable se enquadra em um de três grupos:

  • Error — catástrofe no nível da JVM. Unchecked. Não capture.
  • RuntimeException (uma subclasse de Exception) e todos os seus subtipos. Unchecked.
  • Outras subclasses de Exception. Checked.

A regra é: "RuntimeException e Error, mais seus subtipos, são unchecked; todo o resto sob Throwable é checked." Essa é uma distinção da hierarquia de classes, não um flag de configuração — não há como tornar uma RuntimeException checked ou uma IOException unchecked.

O que "checked" significa no código

Se o corpo de um método lança uma exceção checked, o método deve fazer uma de duas coisas, ou o programa não compilará:

// Option 1: catch it
public void load() {
  try {
    String text = Files.readString(path);   // throws IOException
  } catch (IOException e) {
    // ...
  }
}

// Option 2: declare it
public void load() throws IOException {
  String text = Files.readString(path);
}

O compilador percorre cada método e verifica: para cada exceção checked que pode escapar dele, há um catch envolvente ou uma cláusula throws? Se não houver, erro.

Exceções unchecked não têm esse requisito. Você pode lançar uma IllegalArgumentException de qualquer método sem alterar a assinatura, e qualquer chamador pode capturá-la ou não.

A ideia de design

A motivação por trás das exceções checked foi: algumas falhas são resultados esperados da operação — um arquivo pode não existir, uma rede pode estar fora do ar — e a linguagem deveria garantir que os chamadores pensem sobre isso. Exceções unchecked são para erros de programação — dereferência de null, índice fora dos limites, argumentos ilegais — onde a correção certa é reparar o código, não adicionar um try/catch.

Essa distinção parece clara. Na prática, ela se dobra:

  • Código em camadas multiplica declarações. Uma IOException de baixo nível de um carregador de configuração não fica lá — todo método que a chama precisa tratar ou declarar. Quando a exceção chega ao controller, a assinatura lista seis exceções checked não relacionadas.
  • Lambdas não as suportam bem. Stream.map(this::parseLine) não compilará se parseLine lançar uma exceção checked, porque o apply de Function não a declara.
  • O wrapping está em todo lugar. Uma solução comum é capturar uma exceção checked imediatamente e relançá-la como uma exceção de runtime, o que anula o propósito original.

A maior parte do código Java moderno usa menos exceções checked do que a biblioteca padrão usa — às vezes nenhuma. Novos frameworks como Spring dependem quase inteiramente de exceções de runtime por esse motivo.

Quando usar cada uma

Uma regra prática defensável:

  • Use checked quando o chamador tiver uma estratégia de recuperação realista específica para a falha — por exemplo, repetir em caso de erro de rede, usar um fallback em caso de falha de parse. Force-os a pensar a respeito.
  • Use unchecked quando a falha for um bug (IllegalArgumentException, NullPointerException, IllegalStateException) ou quando nenhum chamador poderia razoavelmente se recuperar.

Na dúvida, prefira unchecked. O custo de errar é menor, e você sempre pode restringir mais tarde.

A experiência do lado do catch

Saber se uma exceção é checked muda como o catch se parece:

// checked: caller must catch or declare
try {
  parser.parse(path);
} catch (IOException e) {
  // handle
}

// unchecked: caller may catch but doesn't have to
try {
  return numbers.get(idx);
} catch (IndexOutOfBoundsException e) {
  return -1;
}

Da perspectiva do catch, não há diferença comportamental — ambos os blocos capturam o que declaram. A divisão só importa no ponto de chamada que não capturou: uma checked forçaria uma declaração throws; uma unchecked se propaga silenciosamente.

Um erro comum: capturar Exception

Como Exception é pai de tipos checked e unchecked (exceto Error), catch (Exception e) corresponde a quase tudo. Isso costuma ser um code smell:

try { complexOperation(); }
catch (Exception e) { log("failed"); }    // hides bugs and real failures alike

O problema não é capturar — é capturar demais. Uma NullPointerException aqui indica um bug; uma IOException indica uma falha real recuperável; uma RuntimeException de uma biblioteca que você não controla pode ser qualquer coisa. Tratá-las de forma idêntica geralmente significa não tratar bem nenhuma delas. Prefira capturar os tipos específicos para os quais você tem um plano.

Um exemplo prático

Um pequeno programa com dois métodos: um declara uma IOException checked e o outro lança uma IllegalArgumentException unchecked. O compilador os trata de forma diferente — apenas a checked força o tratamento no ponto de chamada.

java— editable, runs on the server

Observe três coisas. Remova o throws java.io.IOException de maybeReadFile e o arquivo não compilará. Remova o try/catch envolvente da primeira chamada e o arquivo também não compilará. Mas não há try/catch em torno do requirePositive(-7) final — e isso compila bem, mesmo que a chamada vá travar o programa. Esse é o tratamento assimétrico em uma tela.

O que vem a seguir

Estivemos falando sobre tipos como IOException, RuntimeException, Error — mas como eles se relacionam de fato? A árvore de classes é pequena e vale a pena memorizar. Continue para hierarquia de exceções Java.

Prática

Prática
Qual afirmação sobre exceções checked está correta?
Qual afirmação sobre exceções checked está correta?
Was this page helpful?