Atualizado: 02/11/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

CompletableFuture: Tratamento de Erros e Finalização de Tarefas Assíncronas em Java

O CompletableFuture permite executar tarefas assíncronas e reagir aos seus resultados por meio de callbacks. Mas o que acontece quando ocorre uma exceção durante a execução da tarefa? O Java fornece uma API completa no CompletableFuture para tratar esses casos e garantir que a aplicação continue funcionando corretamente, mesmo em situações de erro.


Tratamento de erros com exceptionally()

Quando uma exceção ocorre dentro de uma tarefa assíncrona, ela é encapsulada em um objeto CompletionException. Para capturar e lidar com esse erro, o CompletableFuture oferece os métodos exceptionally() e exceptionallyCompose():

CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)
CompletableFuture<T> exceptionallyCompose(Function<Throwable, ? extends CompletionStage<T>> fn)
  • exceptionally() permite processar o erro e retornar um valor alternativo.
  • exceptionallyCompose() também processa o erro, mas retorna outro CompletableFuture, o que permite continuar a execução de forma assíncrona.

Exemplo: tratamento de exceção no cálculo de fatorial

import java.util.concurrent.*;
import java.util.function.Supplier;
import java.util.function.Function;
import java.util.function.Consumer;

class Program {

    public static void main(String[] args) throws Exception {

        System.out.println("Main thread started...");

        int number = -5;  // número inválido para o fatorial

        Supplier<Integer> factorialTask = () -> {
            if (number < 1) throw new RuntimeException("Number must be greater than 0");
            int n = 1;
            int result = 1;
            while (n <= number) result *= n++;
            return result;
        };

        Consumer<Integer> printTask = result ->
                System.out.printf("Final result: %d\n", result);

        Function<Throwable, Integer> printException = ex -> {
            System.out.println(ex.getMessage());
            throw new RuntimeException(ex.getMessage());
        };

        ExecutorService executor = Executors.newCachedThreadPool();

        CompletableFuture
                .supplyAsync(factorialTask, executor)
                .exceptionally(printException)
                .thenAccept(printTask);

        System.out.println("Main thread works...");
        System.out.println("Main thread finished...");

        executor.close();
    }
}

Saída esperada:

Main thread started...
java.lang.RuntimeException: Number must be greater than 0
Main thread works...
Main thread finished...

Como o número é inválido, a tarefa lança uma exceção. O método exceptionally() captura essa exceção e executa a função printException, que imprime a mensagem. O callback seguinte (thenAccept) não é executado, pois a exceção foi propagada novamente.

Se o número fosse válido (int number = 5;), a saída seria:

Main thread started...
Final result: 120
Main thread works...
Main thread finished...

Retornando um valor alternativo

Em vez de relançar a exceção, também é possível retornar um valor padrão dentro de exceptionally(), garantindo que a cadeia continue sua execução:

import java.util.concurrent.*;
import java.util.function.Supplier;
import java.util.function.Function;
import java.util.function.Consumer;

class Program {

    public static void main(String[] args) throws Exception {

        int age = -5;

        Supplier<Integer> validateAge = () -> {
            if (age > 110 || age < 1) throw new RuntimeException("Age is invalid");
            return age;
        };

        Consumer<Integer> printAge = result ->
                System.out.printf("Final age: %d\n", result);

        Function<Throwable, Integer> printException = ex -> {
            System.out.println(ex.getMessage());
            return 18; // valor padrão
        };

        ExecutorService executor = Executors.newCachedThreadPool();

        CompletableFuture
                .supplyAsync(validateAge, executor)
                .exceptionally(printException)
                .thenAccept(printAge);

        executor.close();
    }
}

Saída esperada:

java.lang.RuntimeException: Age is invalid
Final age: 18

Neste caso, o erro é capturado, uma mensagem é exibida e o fluxo segue normalmente com o valor padrão 18.


Exceção CompletionException

Quando uma exceção ocorre dentro de um CompletableFuture, ela é envolvida em uma CompletionException. Por isso, a função de tratamento em exceptionally() recebe uma exceção cujo tipo é CompletionException, e não o tipo original.

Para acessar o erro original, pode-se usar o método getCause():

Function<Throwable, Integer> printException = ex -> {
    System.out.println(ex.getCause().getMessage());
    return 18;
};

Saída:

Age is invalid
Final age: 18

Tratando conclusão com whenComplete()

Nem sempre é necessário tratar apenas erros. Às vezes, é desejável executar uma ação ao final da tarefa — seja em caso de sucesso ou falha. O método whenComplete() serve exatamente para isso:

CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action)

O BiConsumer passado ao método recebe dois argumentos:

  • o resultado da tarefa, se bem-sucedida,
  • a exceção, se algo deu errado.

Quando não há erro, o segundo parâmetro é null; quando há falha, o primeiro é null.


Exemplo com whenComplete()

import java.util.concurrent.*;
import java.util.function.Supplier;
import java.util.function.BiConsumer;

class Program {

    public static void main(String[] args) throws Exception {

        System.out.println("Main thread started...");

        ExecutorService executor = Executors.newCachedThreadPool();
        work(executor, 5);
        work(executor, -5);

        System.out.println("Main thread finished...");

        executor.close();
    }

    static void work(Executor executor, int number) {

        Supplier<Integer> factorialTask = () -> {
            if (number < 1)
                throw new RuntimeException("Number must be greater than 0. Invalid number: " + number);
            int n = 1;
            int result = 1;
            while (n <= number) result *= n++;
            return result;
        };

        BiConsumer<Integer, Throwable> completedTask = (result, ex) -> {
            if (ex == null)
                System.out.printf("Factorial of %d is %d\n", number, result);
            else
                System.out.println(ex.getCause().getMessage());
        };

        CompletableFuture
                .supplyAsync(factorialTask, executor)
                .whenComplete(completedTask);
    }
}

Saída típica (ordem não determinística):

Main thread started...
Factorial of 5 is 120
Number must be greater than 0. Invalid number: -5
Main thread finished...

Nesse exemplo, a função completedTask é chamada tanto no sucesso quanto no erro. O parâmetro result contém o valor do fatorial quando não há falha, e o parâmetro ex contém a exceção quando algo dá errado.


Resumo

  • exceptionally() trata erros e pode retornar um valor padrão.
  • exceptionallyCompose() permite continuar o fluxo de forma assíncrona após um erro.
  • CompletionException encapsula o erro original; o método getCause() recupera a exceção real.
  • whenComplete() executa uma ação ao final da tarefa, seja com sucesso ou com falha.
  • Essas ferramentas tornam o CompletableFuture ideal para cenários complexos de execução assíncrona com controle refinado de erros.
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com