Search code examples
asynchronousjava-8uuidcompletable-future

CompletableFuture issue with UUID.randomUUID()


So I decided to start using the CompletableFuture in Java8 and I cannot figure out what wrong with this snippet:

public static void main(String...strings) throws Exception {
    final Supplier<User> makeUserSupplier = () -> makeUser();
    final Supplier<String> uuidSupplier =  () -> makeUUID();

    final CompletableFuture<User> futureUser = CompletableFuture.supplyAsync(makeUserSupplier);
    final CompletableFuture<String> futureUUID = CompletableFuture.supplyAsync(uuidSupplier);

    CompletableFuture.allOf(futureUser, futureUUID)
        .thenApplyAsync(aVoid -> {
            final User user = futureUser.join();
            final String uuid = futureUUID.join();
            return "received user + " + user + " and uuid is " + uuid ;
        })
        .handle((ok, e) -> {
            System.out.println("ok----" + ok);
            System.out.println("e----" + e);
            return null;
        });
}
    private static User makeUser() throws RuntimeException {
//        throw new RuntimeException("lkj");
        return new User(1L, "mm", "ll", "kk");
    }
    private static String makeUUID() throws RuntimeException {
        return UUID.randomUUID().toString();
//        return "dummy";
    }

where the User class is defined as:

@Data
@AllArgsConstructor
public class User {
    private Long id;
    private String username;
    private String password;
    private String role;
}

The behavior I get is:

  • Nothing is printed in my console when I use the UUID.randomUUID().toString() and I get result when I use some random String.
  • The last breakpoint I am able to reach when debugging is the line when I join the futureUUID final String uuid = futureUUID.join(); and then my program stops with no result.

Can someone please try to explain to me why I am getting this strange behavior when using UUID ?

PS: I just started learning CompletableFuture and thought about parallel Futures, then accidentally came to this.

Best regards.


Solution

  • This has nothing to do with the UUID, except that its generation takes some time and you’re not waiting for the completion.

    Since all operations happen in background threads and you’re returning from the main method, the JVM will determine that no non-Daemon thread is running anymore and terminate.

    Simply add a wait-for-completion operation:

    final Supplier<User> makeUserSupplier = () -> makeUser();
    final Supplier<String> uuidSupplier =  () -> makeUUID();
    
    final CompletableFuture<User> futureUser = CompletableFuture.supplyAsync(makeUserSupplier);
    final CompletableFuture<String> futureUUID = CompletableFuture.supplyAsync(uuidSupplier);
    
    CompletableFuture.allOf(futureUser, futureUUID)
        .thenApplyAsync(aVoid -> {
            final User user = futureUser.join();
            final String uuid = futureUUID.join();
            return "received user + " + user + " and uuid is " + uuid ;
        })
        .handle((ok, e) -> {
            System.out.println("ok----" + ok);
            System.out.println("e----" + e);
            return null;
        })
        .join(); // wait for completion
    

    Note that in your original code, you were using .allOf(futureUser, futureUser) instead of .allOf(futureUser, futureUUID), so the chained operation might get executed when futureUUID has not completed yet, which could cause a worker thread getting blocked in the futureUUID.join() call.

    Your code would be much simpler if you used

    final CompletableFuture<User> futureUser = CompletableFuture.supplyAsync(() -> makeUser());
    final CompletableFuture<String> futureUUID = CompletableFuture.supplyAsync(() -> makeUUID());
    
    futureUser.thenCombineAsync(futureUUID, (user, uuid) -> {
            return "received user + " + user + " and uuid is " + uuid;
        })
        .handle((ok, e) -> {
            System.out.println("ok----" + ok);
            System.out.println("e----" + e);
            return null;
        })
        .join(); // wait for completion
    

    which is also immune to the mistake made with allOf, as no join() call is needed within a worker thread.