Kousha
Kousha

Reputation: 36229

Java CompletionStage to CompletionStage<Either> type is lost

Say I have this method:

public CompletionStage<SomeClass> getData() {
    CompletableFuture<SomeClass> future = new CompletableFuture<>();

    return CompletableFuture.runAsync(() -> {
        // Fetch data from some source
        // Then either
        // future.complete(data);
        // or fail
        // future.completeExceptionally(e);
    });

    return future;
}

And now I want to have a method, that calls getData, manipulates, but then returns an CompletionStage<Either<ErrorResponse, Data>>

public CompletionStage<Either<ErrorResponse, Data>> modifyData() {
    return getData()
       .thenCompose(d -> CompletableFuture.completedFuture(Either.right(d)))
       .exceptionally(e -> Either.left(ErrorResponse.create(e)));
}

When I do this, the type inside the exceptionally is lost, and the compiler thinks I am returning a type Either<Object, Data>.

If I however change that code to:

public CompletionStage<Either<ErrorResponse, Data>> modifyData() {
    CompletableFuture<Either<ErrorResponse, Data>> future = new CompetableFuture<>();

    CompletableFuture.runAsync(() -> {
        getData()
           .thenCompose(d -> future.complete(Either.right(d)));
           .exceptionally(e -> 
               future.complete(Either.left(ErrorResponse.create(e)))
    });

    return future;
}

Then it works fine. Why is the type lost?

Upvotes: 0

Views: 1909

Answers (1)

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 280132

You have

public CompletionStage<Either<ErrorResponse, Data>> modifyData() {
    return getData()
       .thenCompose(d -> CompletableFuture.completedFuture(Either.right(d)))
       .exceptionally(e -> Either.left(ErrorResponse.create(e)));
}

thenCompose is a generic method, but you don't provide enough type information for Java to infer that you want its type parameter U to be bound to Either<ErrorResponse, Data>. The call to exceptionally (even if it was generic) also can't feed back type information to the invocation of thenCompose.

Currently, it infers Object for Either's first type parameter. So thenCompose returns a CompletionStage<Either<Object, Data>>. That propagates to exceptionally. Since CompletionStage<Either<Object, Data>> isn't a CompletionStage<Either<ErrorResponse, Data>>, it's an invalid type for a return value.

You can add the necessary type information by providing an explicit type argument.

public CompletionStage<Either<ErrorResponse, Data>> modifyData() {
    return getData()
       .<Either<ErrorResponse, Data>>thenCompose(d -> CompletableFuture.completedFuture(Either.right(d)))
       .exceptionally(e -> Either.left(ErrorResponse.create(e)));
}

or closer to the problematic invocation

public CompletionStage<Either<ErrorResponse, Data>> modifyData() {
    return getData()
       .thenCompose(d -> CompletableFuture.completedFuture(Either.<ErrorResponse, Data>right(d)))
       .exceptionally(e -> Either.left(ErrorResponse.create(e)));
}

Now Java doesn't have to try and guess. It knows what you meant.

Alternatively, I would use CompletableFuture#handle to handle the exceptional case.

return getData()
            .handle((d, e) -> {
                if (e == null) {
                    return Either.right(d);
                }
                return Either.left(ErrorResponse.create(e));
            });
}

The type information is self-contained. The rest is mostly a matter of opinion.

Upvotes: 1

Related Questions