Smajl Smajl -4 years ago 125
Java Question

Ambiguous mapping when using RequestBody annotation

I am trying to create an endpoint in Spring Boot that accepts both single object or an array of these objects. I know that mappings need to have unique signiture so I am wondering what is the correct way to make it work using POJOs?

@RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<String> postSingleFoo(HttpServletRequest request,
@RequestBody(required = true) Foo foo) {
// process
}

@RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<String> postMultiFoo(HttpServletRequest request,
@RequestBody(required = true) Foo[] foo) {
// process
}


Obviously I am getting an exception for ambiguous mapping. But I would still like to use POJOs in my
@RequestBody
annotation since i am performing couple of conversions in them.

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'fooController' method
public void com.usquared.icecream.lrs.controller.FooController.postSingleFoo(javax.servlet.http.HttpServletRequest,java.lang.String)
to {[/foo],methods=[POST]}: There is already 'fooController' bean method


What is the recommended approach to implement such feature correctly?

Answer Source

This is not a problem that can be fixed through Spring MVC. Spring MVC creates mappings from the @RequestMapping annotating your handler methods. These help distinguish how Spring MVC delegates HTTP requests to be handled by your methods. Your current configuration attempts to map two handler methods to the same request details. That can never work.

One solution, assuming you're expecting JSON and working with Jackson, is to configure your ObjectMapper to accept single values as arrays and define a single handler method with an array parameter. For example, you'd keep only this handler method

@RequestMapping(method = { RequestMethod.POST })
public ResponseEntity<String> postMultiFoo(HttpServletRequest request,
        @RequestBody(required = true) Foo[] foo) {
    // process
}

but configure your ObjectMapper as such

ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

The configuration depends on how your application is configured. With Spring Boot, it should be as simple as declaring a @Bean method for ObjectMapper. With your typical Spring MVC application, you'll need to register a MappingJackson2HttpMessageConverter with a custom ObjectMapper.

If your JSON, the request body, contained

{
    "someProperty":"whatever"
}

Jackson would be able to wrap the single value into a Foo[] and Spring MVC would pass that as an argument to your handler method. You can then check the length of the array and act accordingly.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download