1

SpringBoot 2.6.7

The annotation @PostMapping allows multiple values for the consumes attribute to specify different media types / content-types, but it seems that it is impossible to implement a single method that actually allows multiple content/media types.

In the sample code below, separate methods enroll1 and enroll2 uses the same POJO class to capture the request ( Ping class in the sample below ) from either application/x-www-form-urlencoded or application/json, respectively.

Note that the application/x-www-form-urlencoded is also mapped to a POJO, unlike similar questions in stackoverflow where they are mapped to a java.util.Map.

NOTE: Yes, I looked at Supporting application/json and application/x-www-form-urlencoded simultaneously from Spring's rest controller ... but that one uses MultiValueMap for the parameter while I am using a POJO for both media types.

package org.example.demo;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequestMapping("test")
public class SampleController {

//    @PostMapping(value = "ping", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.APPLICATION_JSON_VALUE})
//    public @ResponseBody Pong enroll(@Valid @ModelAttribute @RequestBody Ping ping) {
//        Pong pong = new Pong();
//        pong.setEcho(ping.getText());
//        pong.setSuccess(true);
//        return pong;
//    }

    @PostMapping(value = "ping", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
    public @ResponseBody Pong enroll1(@Valid Ping ping) {
        return doBoth(ping);
    }

    @PostMapping(value = "ping", consumes = {MediaType.APPLICATION_JSON_VALUE})
    public @ResponseBody Pong enroll2(@Valid @RequestBody Ping ping) {
        return doBoth(ping);
    }

    private Pong doBoth(Ping ping) {
        Pong pong = new Pong();
        pong.setEcho(ping.getText());
        pong.setSuccess(true);
        return pong;
    }
}

Notice that the method method accepting application/x-www-form-urlencoded does not need to have the @ModelAttribute annotation on the ping parameter.

package org.example.demo;

import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.NotNull;

@Getter
@Setter
public class Ping {

    @NotNull
    private String text;
}

package org.example.demo;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Pong {
    private String echo;
    private boolean success;
}

While the above works OK, what I would have wanted is for that commented out code to work ( and commenting out / removing the other two methods enroll1() and enroll2() above ):

    @PostMapping(value = "ping", consumes = {
            MediaType.APPLICATION_FORM_URLENCODED_VALUE,
            MediaType.APPLICATION_JSON_VALUE})
    public @ResponseBody Pong enroll(@Valid @ModelAttribute @RequestBody Ping ping) {
        Pong pong = new Pong();
        pong.setEcho(ping.getText());
        pong.setSuccess(true);
        return pong;
    }

In real life, this method and others similar like it have other annotations ( like Swagger / OpenAPI plus other custom annotations ) that would be best to have in just one method instead of being repeated in two methods.

But you cannot annotate the method parameter with both @ModelAttribute and @RequestBody at the same time. If you do, you get :

$ curl -X POST http://localhost:8080/test/ping -H 'content-type: application/x-www-form-urlencoded' -d text=Hello
{"echo":"Hello","success":true}
$
$ curl -X POST http://localhost:8080/test/ping -H 'content-type: application/json' -d '{"text":"Hello"}'
{"timestamp":1651307540135,"status":400,"error":"Bad Request","path":"/test/ping"}

You get the above results as well ( only application/x-www-form-urlencoded works ) if you:

  • Remove the @RequestBody annotation but keep the @ModelAttribute annotation, OR
  • Remove both @RequestBody and @ModelAttribute annotations.

If you remove the @ModelAttribute annotation and leave the @RequestBody annotation on the parameter, you get the results reversed ( but get an HTTP 415 instead of an HTTP 400 ) .The log says Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported:

    @PostMapping(value = "ping", consumes = {
            MediaType.APPLICATION_FORM_URLENCODED_VALUE,
            MediaType.APPLICATION_JSON_VALUE})
    public @ResponseBody Pong enroll(@Valid @RequestBody Ping ping) {
        Pong pong = new Pong();
        pong.setEcho(ping.getText());
        pong.setSuccess(true);
        return pong;
$ curl -X POST http://localhost:8080/test/ping -H 'content-type: application/x-www-form-urlencoded' -d text=Hello
{"timestamp":1651307931120,"status":415,"error":"Unsupported Media Type","path":"/test/ping"}
$ 
$ curl -X POST http://localhost:8080/test/ping -H 'content-type: application/json' -d '{"text":"Hello"}'
{"echo":"Hello","success":true}

Does anyone have a working implementation where a single method have multiple values for the consumes attribute of the @PostMapping annotation, where the request is mapped to a POJO ?

I would like to think that there are, otherwise, it would make no sense to allow it as an array.

jmsjr
  • 148
  • 2
  • 10

0 Answers0