Anti-padrões, 22/10/2026, 7 min de leitura

Os cuidados ao usar catch para fazer rollback de um fluxo

O bloco catch que desfaz uma operação é código que só roda quando tudo já deu errado. É o lugar onde mais se erra.

É comum escrever um fluxo em etapas e, se algo falhar, desfazer o que já foi feito dentro de um catch. A ideia é razoável. A execução costuma esconder quatro problemas.

1. O rollback também falha

try {
    reservarEstoque(pedido);
    cobrar(pedido);
} catch (Exception e) {
    liberarEstoque(pedido); // e se isto lançar exceção?
    throw e;
}

Se liberarEstoque falhar, a exceção original se perde e o sistema fica em estado parcial. Proteja a compensação e preserve a causa:

} catch (Exception e) {
    try {
        liberarEstoque(pedido);
    } catch (Exception ex) {
        e.addSuppressed(ex);
        alertar(pedido, e);
    }
    throw e;
}

2. Engolir a exceção dentro de uma transação

No Spring, capturar a exceção dentro de um método @Transactional sem relançar pode marcar a transação como rollback-only. O resultado é um UnexpectedRollbackException no commit, longe da causa. Além disso, por padrão o Spring só faz rollback para exceções não verificadas. Para as verificadas, declare rollbackFor.

3. Capturar amplo demais

catch (Exception e) trata da mesma forma um erro de negócio, uma falha de rede e um bug. Cada um pede uma reação diferente. Capture o mais específico que você consegue tratar.

4. Rollback só cobre o que é seu

Se o fluxo chamou outro serviço, o catch não desfaz o efeito remoto. Aí é preciso uma compensação explícita, chamada a uma operação de cancelamento, e ela precisa ser idempotente, porque pode ser repetida. Esse é o núcleo do padrão Saga.

Regra prática

  • Compensação deve ser idempotente e com falha tratada.
  • Nunca perca a exceção original.
  • Se o desfazer é crítico, não dependa de um catch em memória. Registre a intenção de compensar de forma durável, por exemplo em uma tabela ou fila, e processe com retentativa.