---
title: "O risco de fechar hierarquias no Java para forçar pattern matching exaustivo"
author: "Pedro Dalben"
date: "2026-10-19T11:02:59Z"
category: "Minecraft & Java"
reading_time: "7 min"
canonical: "https://pedrodalben.com.br/blog/o-risco-de-fechar-hierarquias-no-java-para-forcar-pattern-matching-exaustivo"
---

# O risco de fechar hierarquias no Java para forçar pattern matching exaustivo

> Adotar sealed classes traz garantias estáticas no switch, mas introduz armadilhas de compatibilidade binária e elimina a extensão por terceiros.

**Autor:** Pedro Dalben (CTO no Integrar Plus)  
**Publicado em:** 19 de October de 2026  
**URL Canônica Oficial:** [https://pedrodalben.com.br/blog/o-risco-de-fechar-hierarquias-no-java-para-forcar-pattern-matching-exaustivo](https://pedrodalben.com.br/blog/o-risco-de-fechar-hierarquias-no-java-para-forcar-pattern-matching-exaustivo)  

---


A promessa parecia irresistível. O Java finalmente incorporou tipos selados e pattern matching exaustivo no comando switch, permitindo que a gente abandone cláusulas default desnecessárias e delegue ao compilador a tarefa de conferir se cada subtipo foi contemplado. Para quem passa anos modelando mensagens de rede, pacotes de protocolo ou máquinas de estado, a ideia de transformar hierarquias de classes em uniões discriminadas soa como o melhor dos dois mundos: temos o rigor dos tipos algébricos do paradigma funcional sem precisar abandonar o ecossistema da JVM.

O problema começa quando essa garantia estática encontra o mundo real de bibliotecas compartilhadas, carregamento dinâmico de classes e ecossistemas baseados em plugins. A compilação avisa quando falta um caso, mas esse escudo desaparece no instante em que o artefato consumidor roda contra uma versão mais recente do artefato produtor sem ter passado por um novo build.

## Duas visões sobre a mesma sintaxe

A discussão recente em torno dessa mecânica expõe um racha nítido entre os desenvolvedores. De um lado, há quem defenda que a combinação de `sealed` com pattern matching é a melhor evolução da linguagem em anos, justamente por manter o Java reconhecível sem recorrer aos excessos de complexidade vistos em Scala ou C#. Para esse grupo, fechar o escopo de herança traz clareza imediata para mensagens orientadas a eventos e controle de acesso, eliminando o código defensivo que polui métodos com exceções manuais para casos não mapeados.

Do outro lado, engenheiros mais céticos apontam que a exaustividade é uma ilusão perigosa quando existe desacoplamento entre quem publica o jar e quem o consome. Se uma biblioteca adiciona uma nova subclasse permitida em uma atualização pontual, o bytecode do consumidor continua sendo binariamente compatível na teoria, mas vai falhar em tempo de execução ao disparar um `MatchException` assim que encontrar o novo tipo no switch.

Eu concordo com os céticos. Tratar tipos selados como uma substituição universal para o polimorfismo aberto ignora a premissa que sempre sustentou a estabilidade da JVM: a compatibilidade binária entre versões independentes.

## A inversão do polimorfismo clássico

No polimorfismo tradicional da orientação a objetos, a hierarquia é aberta por definição. Se você declara uma interface de componente de bloco ou de manipulador de inventário, o contrato define os métodos que cada implementação precisa fornecer. Quando alguém cria uma nova variante, essa nova classe implementa o comportamento esperado e o código existente consome o objeto sem precisar ser alterado nem recompilado. É a aplicação direta do princípio aberto/fechado: o sistema aceita novos tipos sem mexer no que já funciona.

Com `sealed classes`, a dinâmica se inverte por completo. A responsabilidade pelo comportamento sai dos subtipos e migra para os blocos de switch espalhados pelo código que consome os dados.

```java
public sealed interface Packet permits LoginPacket, MovementPacket, ChatPacket {}

public final class LoginPacket implements Packet {}
public final class MovementPacket implements Packet {}
public final class ChatPacket implements Packet {}
```

Ao processar esse pacote, a exaustividade parece elegante:

```java
public void process(Packet packet) {
    switch (packet) {
        case LoginPacket l -> handleLogin(l);
        case MovementPacket m -> handleMovement(m);
        case ChatPacket c -> handleChat(c);
    }
}
```

O compilador garante que não precisamos de um bloco `default`. Não há linha de código sobrando. A estrutura é limpa, direta e estritamente verificada durante o build.

Agora imagine esse modelo em uma API de servidor de Minecraft ou em um framework de processamento distribuído onde terceiros escrevem módulos carregados em tempo de execução. No momento em que a interface é selada com uma lista fixa de permissões, a extensão externa é bloqueada. Ninguém fora do pacote ou do módulo original pode adicionar uma nova implementação de `Packet`. Se o seu objetivo era impedir herança indiscriminada, a restrição funciona. Se o seu objetivo era permitir que plugins registrassem comportamentos novos de jogo ou novos tipos de entidades, a arquitetura foi inviabilizada.

## A armadilha da compatibilidade binária

O ponto mais crítico não reside na perda de extensibilidade, mas no comportamento do bytecode gerado. Quando o compilador Java valida um switch exaustivo sobre um tipo selado, ele confere a lista de subclasses permitidas naquele momento exato da compilação e gera uma tabela de decisão baseada nessa premissa.

Caso o produtor da biblioteca decida adicionar uma quarta subclasse em uma versão posterior, por exemplo um `InteractionPacket`, ele não quebrou nenhuma assinatura pública existente. O jar compila perfeitamente. O consumidor que utiliza essa biblioteca atualizada em produção sem recompilar seu próprio código vai rodar até a primeira ocorrência do novo pacote, momento em que a JVM lançará `java.lang.MatchException`.

Esse dilema já causou discussões na própria evolução da biblioteca padrão do Java, como nas discussões de especificações recentes de decodificação criptográfica. Quando a equipe da plataforma percebeu que expor uma classe selada pública com subclasses abertas levaria os consumidores a escrever switches exaustivos que quebrariam caso novas implementações surgissem em versões futuras do JDK, a saída precisou ser contornada com subclasses não públicas para desativar a exaustividade automática fora da biblioteca.

Isso revela uma fragilidade evidente. A compilação finge entregar uma prova matemática de cobertura, mas essa prova só vale enquanto o grafo de dependências for imutável e compilado em conjunto.

## Onde a escolha faz sentido

Apesar dos riscos de compatibilidade em APIs públicas, tipos selados têm utilidade prática real quando aplicados dentro das fronteiras corretas. A decisão depende de onde a fronteira arquitetural está desenhada.

Existem cenários onde fechar a hierarquia é a decisão correta:

- Máquinas de estado internas onde os estados possíveis são finitos e pertencem a uma única unidade de negócio.
- Árvores de sintaxe abstrata (AST) em analisadores léxicos e compiladores internos, onde o conjunto de nós é fixo.
- Protocolos de rede privados e mensagens de filas internas onde produtor e consumidor compartilham o mesmo ciclo de release e deploy.

Por outro lado, o polimorfismo aberto clássico continua sendo a única opção viável em outros contextos:

- Interfaces de provedores de serviço (SPI) destinadas a bibliotecas e ecossistemas abertos.
- APIs de extensibilidade para plugins e modificações em servidores de aplicação ou motores de jogos.
- Contratos de domínio em sistemas com múltiplos serviços autônomos que evoluem em cadências diferentes.

Se você está escrevendo código que consome tipos selados vindos de uma dependência de terceiros, confiar cegamente na exaustividade estática do switch é arriscado. Nessas situações, manter um caso de fallback defensivo para tratar subtipos desconhecidos deixa de ser preciosismo e passa a ser a única barreira contra falhas imprevistas em produção, mesmo que o compilador insista que aquela linha nunca será executada.

## Fontes

- [https://neilmadden.blog/2026/04/24/java-sealed-classes-and-exhaustive-pattern-matching/](https://neilmadden.blog/2026/04/24/java-sealed-classes-and-exhaustive-pattern-matching/)
