Search code examples
springspring-bootspring-webfluxillegalstateexception

How to use @RequestBody in Spring Webflux and avoid IllegalStateException?


I'm new to Webflux and Spring in general and I've been having troubles setting a simple server that handles a POST request:

WebfluxtestApplication.java

package com.test.webfluxtest;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@SpringBootApplication
@RestController
public class WebfluxtestApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebfluxtestApplication.class, args);
    }

    @PostMapping
    public Mono<Person> processPerson(@RequestBody Mono<Person> personMono){
        Person person = personMono.block();
        person.setAge(42);
        return Mono.just(person);
    }

}

With Person being a follows:

Person.java

package com.test.webfluxtest;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Person {

  private String name;
  private int age;

  public Person(@JsonProperty String name, @JsonProperty int age){
    this.name = name;
    this.age = age;
  }

  public void setName(String name) {
    this.name = name;
  }

  public void setAge(int age) {
    this.age = age;
  }

  @Override
  public String toString() {
    return this.name + "  " + this.age;
  }
}

However, when I send the POST request, I get an java.lang.IllegalStateException. More specifically here's the top of stack trace:

java.lang.IllegalStateException: In a WebFlux application, form data is accessed via ServerWebExchange.getFormData().
    at org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver.readBody(AbstractMessageReaderArgumentResolver.java:158) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
    Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
    |_ checkpoint ? HTTP POST "/" [ExceptionHandlingWebHandler]
Stack trace:
        at org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver.readBody(AbstractMessageReaderArgumentResolver.java:158) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at org.springframework.web.reactive.result.method.annotation.AbstractMessageReaderArgumentResolver.readBody(AbstractMessageReaderArgumentResolver.java:126) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at org.springframework.web.reactive.result.method.annotation.RequestBodyMethodArgumentResolver.resolveArgument(RequestBodyMethodArgumentResolver.java:64) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:123) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at org.springframework.web.reactive.result.method.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:200) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at org.springframework.web.reactive.result.method.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:137) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter.lambda$handle$1(RequestMappingHandlerAdapter.java:200) ~[spring-webflux-5.2.1.RELEASE.jar:5.2.1.RELEASE]
        at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44) ~[reactor-core-3.3.0.RELEASE.jar:3.3.0.RELEASE]

The problem seems to be that I'm using the @RequestBody tag directly instead of accessing the form data via ServerWebExchange. However, I've seen multiple examples online where @RequestBody was used together with WebFlux without any issues. For example the framework documentation of Spring itself.

There seems to have been similar issues where using the tag produces IllegalStateExceptions (like here), but with different exception's message.

I used the spring-boot initializer for creating the grade file of the project, but have not changed it myself.

Does anybody have any idea what the problem is and how I could solve it?

EDIT

Thanks to Aviad Levy I was able to find the problem. Namely, the POST request was not properly formatted, as the body was being sent encoded in the url. To make it work, I had to make sure the request body was a JSON object and the Content-Type header was set to application/json. I'm just still confused as to why using the wrong format throws the exception with that particular message.


Solution

  • Update: this answer assumes that you want to post form data, which was the assumption based on the error message.

    The short answer is to use @ModelAttribute. In addition you can't block, not without switching threads (e.g. via publishOn() operator), but here you don't have to do that. This works:

    @PostMapping("/test")
    public Mono<Person> processPerson(@ModelAttribute Mono<Person> personMono){
        return personMono.doOnNext(p -> p.setAge(42));
    }
    

    The long answer is that @RequestBody supports only MultiValueMap<String,String> for form data. For binding form data onto an Object you need to use @ModelAttribute. In this case however the error message you're seeing is about something different. It is trying to prevent the body from being consumed twice, once via code that access form parameters via exchange.getFormData() and a second time by declaring a controller method parameter. So the error message would still apply but only if you were trying to bind to MultiValueMap.

    There is room for improvement. In the very least, the error message can be improved. Additionally @RequestBody support for form data could be improved since clearly what you're trying to do is possible and you shouldn't have to remember to switch out annotations.

    Would you mind creating an issue in https://github.com/spring-projects/spring-framework/issues?