Gautam Garg
Gautam Garg

Reputation: 66

Spring-Boot WebClient block() method returns error java.lang.IllegalStateException

I'm trying to fetch value(string) using Spring WebFlux WebClient, (Using SpringBoot version 2.4.5,)

@GetMapping("/test")
public Mono<String> getData(){
    WebClient webClient = WebClient.create("http://localhost:9999");
    Mono<String> stringMono = webClient.get()
            .uri("/some/thing")
            .retrieve()
            .bodyToMono(String.class);
    stringMono.subscribe( System.out::println);
    System.out.println("Value : " + stringMono.block()); // this doesn't work,  expecting to return ResponseBody as "Hello World" ,
    return stringMono;
}

But getting below error

2021-05-11 20:02:15.521 ERROR 55613 --- [ctor-http-nio-2] a.w.r.e.AbstractErrorWebExceptionHandler : [19114471-1]  500 Server Error for HTTP GET "/test"
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2
    at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83) ~[reactor-core-3.4.3.jar:3.4.3]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ⇢ HTTP GET "/test" [ExceptionHandlingWebHandler]
Stack trace:
        at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83) ~[reactor-core-3.4.3.jar:3.4.3]
        at reactor.core.publisher.Mono.block(Mono.java:1703) ~[reactor-core-3.4.3.jar:3.4.3]
        at com.example.demo.DemoApplication.getData(DemoApplication.java:28) ~[main/:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
        at org.springframework.web.reactive.result.method.InvocableHandlerMethod.lambda$invoke$0(InvocableHandlerMethod.java:146) ~[spring-webflux-5.3.4.jar:5.3.4]
        at reactor.core.publisher.FluxFlatMap.trySubscribeScalarMap(FluxFlatMap.java:151) ~[reactor-core-3.4.3.jar:3.4.3]

Reference for block method - https://docs.spring.io/spring-framework/docs/current/reference/html/web-reactive.html#webflux-client-synchronous

Upvotes: 4

Views: 16473

Answers (1)

Michael Berry
Michael Berry

Reputation: 72294

When using Webflux, the whole idea is that you don't block - you'll cause massive performance problems if you do (see here for a related answer that explains why) so the framework explicitly disallows it, throwing an exception if you try.

You also shouldn't subscribe manually - while not a "mortal sin" in the reactive world as blocking is, that's certainly another red flag. Subscription is handled by the framework. You just need to return the Mono, and Webflux will subscribe when required to handle your request. In your case your manual subscription means the entire chain will actually be executed twice for each request - once to print the result out to the terminal, and once to return the result to the /test endpoint.

Instead, if you want a "side-effect" like this (printing out the value when you have it) then you need to alter the reactive chain to do so, using the doOnNext() operator. This means you can then do:

return webClient.get()
          .uri("/some/thing")
          .retrieve()
          .bodyToMono(String.class)
          .doOnNext(s -> System.out.println("Value: " + s));

This will ensure the value is printed out to the terminal, but without blocking, and without manually subscribing.

Upvotes: 6

Related Questions