You need to have your controller method consume MediaType.MULTIPART_FORM_DATA_VALUE,
@PostMapping(value = "/test", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) ......
You also need to add a MappingJackson2HttpMessageConverter support application/octet-stream. In this answer,
- I configure it by using
WebMvcConfigurer#extendMessageConverters so that I can keep the default configuration of the other converters.(Spring MVC is configured with Spring Boot’s converters). - I create the converter from the
ObjectMapper instance used by Spring.
[For more information]
Spring Boot Reference Documentation - Spring MVC Auto-configuration
How do I obtain the Jackson ObjectMapper in use by Spring 4.1?
Why does Spring Boot change the format of a JSON response even when a custom converter which never handles JSON is configured?
@Configuration public class MyConfigurer implements WebMvcConfigurer { @Autowired private ObjectMapper objectMapper; @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { ReadOnlyMultipartFormDataEndpointConverter converter = new ReadOnlyMultipartFormDataEndpointConverter( objectMapper); List<MediaType> supportedMediaTypes = new ArrayList<>(); supportedMediaTypes.addAll(converter.getSupportedMediaTypes()); supportedMediaTypes.add(MediaType.APPLICATION_OCTET_STREAM); converter.setSupportedMediaTypes(supportedMediaTypes); converters.add(converter); } }
[NOTE]
Also you can modify the behavior of your converter by extending it.
In this answer, I extends MappingJackson2HttpMessageConverter so that
- it reads data only when the mapped controller method consumes just
MediaType.MULTIPART_FORM_DATA_VALUE - it doesn't write any response(another converter do that).
public class ReadOnlyMultipartFormDataEndpointConverter extends MappingJackson2HttpMessageConverter { public ReadOnlyMultipartFormDataEndpointConverter(ObjectMapper objectMapper) { super(objectMapper); } @Override public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) { // When a rest client(e.g. RestTemplate#getForObject) reads a request, 'RequestAttributes' can be null. RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); if (requestAttributes == null) { return false; } HandlerMethod handlerMethod = (HandlerMethod) requestAttributes .getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); if (handlerMethod == null) { return false; } RequestMapping requestMapping = handlerMethod.getMethodAnnotation(RequestMapping.class); if (requestMapping == null) { return false; } // This converter reads data only when the mapped controller method consumes just 'MediaType.MULTIPART_FORM_DATA_VALUE'. if (requestMapping.consumes().length != 1 || !MediaType.MULTIPART_FORM_DATA_VALUE.equals(requestMapping.consumes()[0])) { return false; } return super.canRead(type, contextClass, mediaType); } // If you want to decide whether this converter can reads data depending on end point classes (i.e. classes with '@RestController'/'@Controller'), // you have to compare 'contextClass' to the type(s) of your end point class(es). // Use this 'canRead' method instead. // @Override // public boolean canRead(Type type, Class<?> contextClass, MediaType mediaType) { // return YourEndpointController.class == contextClass && super.canRead(type, contextClass, mediaType); // } @Override protected boolean canWrite(MediaType mediaType) { // This converter is only be used for requests. return false; } }
The causes of 415 errors
When your controller method consumes MediaType.APPLICATION_OCTET_STREAM_VALUE, it doesn't handle a request with Content-Type: multipart/form-data;. Therefore you get 415.
On the other hand, when your controller method consumes MediaType.MULTIPART_FORM_DATA_VALUE, it can handle a request with Content-Type: multipart/form-data;. However JSON without Content-Type is not handled depending on your configuration.
When you annotate a method argument with @RequestPart annotation,
RequestPartMethodArgumentResolver parses a request. RequestPartMethodArgumentResolver recognizes content-type as application/octet-stream when it is not specified. RequestPartMethodArgumentResolver uses a MappingJackson2HttpMessageConverter to parse a reuqest body and get JSON. - By default configuration
MappingJackson2HttpMessageConverter supports application/json and application/*+json only. - (As far as I read your question) Your
MappingJackson2HttpMessageConverters don't seem to support application/octet-stream.(Therefore you get 415.)
Conclusion
Therefore I think you can successfully handle a request by letting MappingJackson2HttpMessageConverter(an implementation of HttpMessageConverter) to support application/octet-stream like above.
[UPDATE 1]
If you don't need to validate MyModel with @Valid annotation and simply want to convert the JSON body to MyModel, @RequestParam can be useful.
If you choose this solution, you do NOT have to configure MappingJackson2HttpMessageConverter to support application/octet-stream.
You can handle not only JSON data but also file data using this solution.
@PostMapping(value = "/test", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public void test(@RequestParam(value = "MyModel") Part part) throws IOException { // 'part' is an instance of 'javax.servlet.http.Part'. // According to javadoc of 'javax.servlet.http.Part', // 'The part may represent either an uploaded file or form data' try (InputStream is = part.getInputStream()) { ObjectMapper objectMapper = new ObjectMapper(); MyModel myModel = objectMapper.readValue(part.getInputStream(), MyModel.class); ..... } ..... }
See Also
Javadoc of RequestPartMethodArgumentResolver
Javadoc of MappingJackson2HttpMessageConverter
Content type blank is not supported (Related question)
Spring Web MVC - Multipart