5

I have a list of usernames and want to fetch user details from the remote service without blocking the main thread. I'm using Spring's reactive client WebClient. For the response, I get Mono then subscribe it and print the result.

private Mono<User> getUser(String username) {
    return webClient
            .get()
            .uri(uri + "/users/" + username)
            .retrieve()
            .bodyToMono(User.class)
            .doOnError(e -> 
                logger.error("Error on retrieveing a user details {}", username));
}

I have implemented the task in two ways:

Using Java stream

usernameList.stream()
          .map(this::getUser)
          .forEach(mono ->
                mono.subscribe(System.out::println));

Using Flux.fromIterable:

Flux.fromIterable(usernameList)
        .map(this::getUser)
        .subscribe(mono ->
                mono.subscribe(System.out::println));

It seems the main thread is not blocked in both ways. What is the difference between Java Stream and Flux.fromIterable in this situation? If both are doing the same thing, which one is recommended to use?

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
Sarvar Nishonboyev
  • 12,262
  • 10
  • 69
  • 70

1 Answers1

3

There are not huge differences between both variants. The Flux.fromIterable variant might give your more options and control about concurrency/retries, etc - but not really in this case because calling subscribe here defeats the purpose.

Your question is missing some background about the type of application you're building and in which context these calls are made. If you're building a web application and this is called during request processing, or a batch application - opinions might vary.

In general, I think applications should stay away from calling subscribe because it disconnects the processing of that pipeline from the rest of the application: if an exception happens, you might not be able to report it because the resource to use to send that error message might be gone at that point. Or maybe the application is shutting down and you have no way to make it wait the completion of that task.

If you're building an application that wants to kick off some work and that its result is not useful to the current operation (i.e. it doesn't matter if that work completes or not during the lifetime of the current operation), then subscribe might be an option.

In that case, I'd try and group all operations in a single Mono<Void> operation and then trigger that work:

Mono<Void> logUsers = Flux.fromIterable(userNameList)
    .map(name -> getUser(name))
    .doOnNext(user -> System.out.println(user)) // assuming this is non I/O work
    .then();
logUsers.subscribe(...);

If you're concerned about consuming server threads in a web application, then it's really different - you might want to get the result of that operation to write something to the HTTP response. By calling subscribe, both tasks are now disconnected and the HTTP response might be long gone by the time that work is done (and you'll get an error while writing to the response).

In that case, you should chain the operations with Reactor operators.

Brian Clozel
  • 56,583
  • 15
  • 167
  • 176
  • It is actually a batch application that sends emails to the users recommending some kind of information. Your sample is really useful! – Sarvar Nishonboyev Dec 05 '19 at 05:17
  • One more question I have. There is another API that I can send multiple usernames and get multiple users' info in response. Do you think it is better to get all the results at once and deal with it or send multiple parallel requests? – Sarvar Nishonboyev Dec 05 '19 at 05:21
  • It depends on what that method is doing; if it's sending multiple requests in parallel it might be equivalent. If there's a special API that makes a single HTTP call it' probably better. – Brian Clozel Dec 05 '19 at 09:18