相关文章推荐
大鼻子的荒野  ·  rk3588 repo ...·  1 年前    · 
憨厚的松树  ·  python ...·  1 年前    · 
文质彬彬的眼镜  ·  如何用python ...·  1 年前    · 
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams
POST /test HTTP/1.1
user-agent: Dart/2.8 (dart:io)
content-type: multipart/form-data; boundary=--dio-boundary-3791459749
accept-encoding: gzip
content-length: 151
host: 192.168.0.107:8443
----dio-boundary-3791459749
content-disposition: form-data; name="MyModel"
{"testString":"hello world"}
----dio-boundary-3791459749--

But unfortunately this Spring endpoint:

@PostMapping(value = "/test", consumes = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void test(@Valid @RequestPart(value = "MyModel") MyModel myModel) {
    String testString = myModel.getTestString();

returns 415 error:

Content type 'multipart/form-data;boundary=--dio-boundary-2534440849' not supported

to the client.

And this(same endpoint but with the consumes = MULTIPART_FORM_DATA_VALUE):

@PostMapping(value = "/test", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public void test(@Valid @RequestPart(value = "MyModel") MyModel myModel) {
    String testString = myModel.getTestString();

again returns 415 but, with this message:

Content type 'application/octet-stream' not supported

I already successfully used this endpoint(even without consumes) with this old request:

POST /test HTTP/1.1
Content-Type: multipart/form-data; boundary=62b81b81-05b1-4287-971b-c32ffa990559
Content-Length: 275
Host: 192.168.0.107:8443
Connection: Keep-Alive
Accept-Encoding: gzip
User-Agent: okhttp/3.8.0
--62b81b81-05b1-4287-971b-c32ffa990559
Content-Disposition: form-data; name="MyModel"
Content-Transfer-Encoding: binary
Content-Type: application/json; charset=UTF-8
Content-Length: 35
{"testString":"hello world"}
--62b81b81-05b1-4287-971b-c32ffa990559--

But unfortunately now I need to use the first described request and I can't add additional fields to it.

So, I need to change the Spring endpoint, but how?

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.)
  • [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

    Thanks, it's works. But now I have a different problem: now my other endpoints(even without multipart) sometimes respond not in the correct format. How can I specify that this bean should only be used in the endpoint with the consumes = MediaType.MULTIPART_FORM_DATA_VALUE and only for the requests, not for the responses? – konstantin_doncov Feb 24, 2020 at 8:26 I added ‘UPDATE 1’ to my answer. This is just a quick note to tell you a possible solution. This solution has some limitation (e.g. you can’t do validation with @Valid ) so I am still thinking about whether we have better solutions. – Tomoki Sato Feb 24, 2020 at 10:36 I will need to send files(e.g. images) to the server, so I'm not sure about this solution. I think I need to use part. – konstantin_doncov Feb 24, 2020 at 10:52 [1/2] Sorry for the long delay - I thought that I can fix the problem on the client, but unfortunately it didn’t work out. Let's discuss the solution with the converter. I'm still facing the problem with the wrong response of the other endpoints, e.g. my endpoints returns Date in milliseconds, but before it was in the String. It's not the problem, but it shows that this convertor impacts other endpoints. – konstantin_doncov Feb 28, 2020 at 20:23 [2/2] I added two files: MultipartConfigurer and ReadOnlyMultipartFormDataEndpointConverter, and changed MappingJackson2HttpMessageConverter to ReadOnlyMultipartFormDataEndpointConverter in the MultipartConfigurer, but still have the same problem with the dates(or even maybe with something else that I have not yet noticed). About your question: I want to read both file and JSON in the same method, e.g. for registration - user sends account information and avatar in the same time. – konstantin_doncov Feb 28, 2020 at 20:23

    Thanks for contributing an answer to Stack Overflow!

    • Please be sure to answer the question. Provide details and share your research!

    But avoid

    • Asking for help, clarification, or responding to other answers.
    • Making statements based on opinion; back them up with references or personal experience.

    To learn more, see our tips on writing great answers.