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.