Home > Software engineering >  Content type multipart/mixed not supported / 415 UNSUPPORTED_MEDIA_TYPE (after Spring Spring Boot
Content type multipart/mixed not supported / 415 UNSUPPORTED_MEDIA_TYPE (after Spring Spring Boot

Time:05-16

I was previously using the following versions of Spring Spring Boot:

<springVersion>5.0.16.RELEASE</springVersion>
<springBootVersion>2.0.5.RELEASE</springBootVersion>

And the following classes, which handled a multipart/mixed upload. This is all working fine using the Spring versions above:

StagingApi.java

import io.swagger.annotations.*;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.Valid;
import javax.validation.constraints.*;
@javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2022-05-10T11:14:13.587 01:00[Europe/London]")
@Api(value = "Staging", description = "API")
public interface StagingApi {

    @ApiOperation(value = "", nickname = "createOrReplaceBatch", notes = "", tags={  })
    @ApiResponses(value = { 
        @ApiResponse(code = 200, message = ""),
        @ApiResponse(code = 400, message = ""),
        @ApiResponse(code = 500, message = "") })
    @RequestMapping(value = "/batches/{batchId}",
        consumes = { "multipart/mixed" },
        method = RequestMethod.PUT)
    ResponseEntity<Void> createOrReplaceBatch(
        @ApiParam(value = "" ,required=true) @RequestHeader(value="X-TENANT-ID", required=true) String X_TENANT_ID,
        @Size(min=1) @ApiParam(value = "",required=true) @PathVariable("batchId") String batchId,
        @ApiParam(value = ""  )  @Valid @RequestBody Object body);
}

StagingController.java

public ResponseEntity<Void> createOrReplaceBatch(
    @ApiParam(value = "Identifies the tenant making the request.", required = true)
    @RequestHeader(value = "X-TENANT-ID", required = true) String X_TENANT_ID,
    @Size(min = 1) @ApiParam(value = "Identifies the batch.", required = true)
    @PathVariable("batchId") String batchId,
    Object body)
{

    final ServletFileUpload fileUpload = new ServletFileUpload();
    final FileItemIterator fileItemIterator;
    try {
        fileItemIterator = fileUpload.getItemIterator(request);
    } catch (final FileUploadException | IOException ex) {
        LOGGER.error("Error getting FileItemIterator", ex);
        throw new WebMvcHandledRuntimeException(HttpStatus.BAD_REQUEST, ex.getMessage());
    }
    try {
        batchDao.saveFiles(new TenantId(X_TENANT_ID), new BatchId(batchId), fileItemIterator);
        return new ResponseEntity<>(HttpStatus.OK);
    } catch (final InvalidTenantIdException | InvalidBatchIdException | IncompleteBatchException | InvalidBatchException ex) {
        throw new WebMvcHandledRuntimeException(HttpStatus.BAD_REQUEST, ex.getMessage());
    } catch (final StagingException ex) {
        throw new WebMvcHandledRuntimeException(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
    }
}

Log of successfully handled request:

[2022-05-10 11:12:14.861Z #bc7.042 DEBUG -            -   ] o.s.w.s.m.m.a.RequestMappingHandlerMapping: Looking up handler method for path /batches/test-batch
[2022-05-10 11:12:14.861Z #bc7.042 DEBUG -            -   ] o.s.w.a.FixedContentNegotiationStrategy: Requested media types: [application/json, */*]
[2022-05-10 11:12:14.861Z #bc7.042 DEBUG -            -   ] o.s.w.a.FixedContentNegotiationStrategy: Requested media types: [application/json, */*]
[2022-05-10 11:12:14.862Z #bc7.042 DEBUG -            -   ] o.s.w.a.FixedContentNegotiationStrategy: Requested media types: [application/json, */*]
[2022-05-10 11:12:14.863Z #bc7.042 DEBUG -            -   ] o.s.w.s.m.m.a.RequestMappingHandlerMapping: Returning handler method [public org.springframework.http.ResponseEntity<java.lang.Void> com.acme.corp.staging.StagingController.createOrReplaceBatch(java.lang.String,java.lang.String,java.lang.Object)]
[2022-05-10 11:12:14.863Z #bc7.042 DEBUG -            -   ] o.s.b.f.s.DefaultListableBeanFactory: Returning cached instance of singleton bean 'stagingController'
[2022-05-10 11:12:14.866Z #bc7.042 DEBUG -            -   ] o.s.b.w.s.f.OrderedRequestContextFilter: Bound request context to thread: org.apache.catalina.connector.RequestFacade@37f2254a
[2022-05-10 11:12:14.870Z #bc7.042 DEBUG -            -   ] o.s.w.s.DispatcherServlet: DispatcherServlet with name 'dispatcherServlet' processing PUT request for [/batches/test-batch]
[2022-05-10 11:12:14.871Z #bc7.042 DEBUG -            -   ] o.s.w.s.m.m.a.RequestMappingHandlerMapping: Looking up handler method for path /batches/test-batch
[2022-05-10 11:12:14.871Z #bc7.042 DEBUG -            -   ] o.s.w.a.FixedContentNegotiationStrategy: Requested media types: [application/json, */*]
[2022-05-10 11:12:14.871Z #bc7.042 DEBUG -            -   ] o.s.w.a.FixedContentNegotiationStrategy: Requested media types: [application/json, */*]
[2022-05-10 11:12:14.871Z #bc7.042 DEBUG -            -   ] o.s.w.a.FixedContentNegotiationStrategy: Requested media types: [application/json, */*]
[2022-05-10 11:12:14.871Z #bc7.042 DEBUG -            -   ] o.s.w.s.m.m.a.RequestMappingHandlerMapping: Returning handler method [public org.springframework.http.ResponseEntity<java.lang.Void> com.acme.corp.staging.StagingController.createOrReplaceBatch(java.lang.String,java.lang.String,java.lang.Object)]
[2022-05-10 11:12:14.871Z #bc7.042 DEBUG -            -   ] o.s.b.f.s.DefaultListableBeanFactory: Returning cached instance of singleton bean 'stagingController'
[2022-05-10 11:12:14.932Z #bc7.042 DEBUG -            10c9] o.s.w.a.FixedContentNegotiationStrategy: Requested media types: [application/json, */*]
[2022-05-10 11:12:14.933Z #bc7.042 DEBUG -        -       ] o.s.w.s.DispatcherServlet: Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
[2022-05-10 11:12:14.933Z #bc7.042 DEBUG -        -       ] o.s.w.s.DispatcherServlet: Successfully completed request

However, when I try to update the versions of Spring and Spring Boot to:

<springVersion>5.2.4.RELEASE</springVersion>
<springBootVersion>2.2.6.RELEASE</springBootVersion>

the same request fails with a 415 UNSUPPORTED_MEDIA_TYPE response.

Log of failed request:

[2022-05-10 11:31:52.158Z #dc2.024 INFO  -            -   ] o.a.c.c.C..localhost.: Initializing Spring DispatcherServlet 'dispatcherServlet'
[2022-05-10 11:31:52.158Z #dc2.024 INFO  -            -   ] o.s.w.s.DispatcherServlet: Initializing Servlet 'dispatcherServlet'
[2022-05-10 11:31:52.158Z #dc2.024 DEBUG -            -   ] o.s.w.s.DispatcherServlet: Detected StandardServletMultipartResolver
[2022-05-10 11:31:52.162Z #dc2.024 DEBUG -            -   ] o.s.w.s.DispatcherServlet: enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
[2022-05-10 11:31:52.162Z #dc2.024 INFO  -            -   ] o.s.w.s.DispatcherServlet: Completed initialization in 4 ms
[2022-05-10 11:31:52.165Z #dc2.024 DEBUG -            -   ] o.s.w.s.DispatcherServlet: PUT "/batches/test-batch", parameters={}
[2022-05-10 11:31:52.192Z #dc2.024 DEBUG -            -   ] o.s.w.s.m.m.a.RequestMappingHandlerMapping: Mapped to com.acme.corp.staging.StagingController#createOrReplaceBatch(String, String, Object)
[2022-05-10 11:31:52.202Z #dc2.024 DEBUG -            a17e] o.s.w.s.m.m.a.ServletInvocableHandlerMethod: Could not resolve parameter [2] in public org.springframework.http.ResponseEntity<java.lang.Void> com.acme.corp.staging.StagingController.createOrReplaceBatch(java.lang.String,java.lang.String,java.lang.Object): Content type 'multipart/mixed;boundary=efb8369b-607b-4dcf-9f92-e6cd8244db1e;charset=UTF-8' not supported
[2022-05-10 11:31:52.204Z #dc2.024 DEBUG -            a17e] o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver: Using @ExceptionHandler acme.corp.staging.exceptions.WebMvcExceptionHandler#handleException(Exception, WebRequest)
[2022-05-10 11:31:52.206Z #dc2.024 DEBUG -            a17e] o.s.w.s.m.m.a.HttpEntityMethodProcessor: No match for [application/json, */*], supported: []
[2022-05-10 11:31:52.206Z #dc2.024 DEBUG -            a17e] o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver: Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/mixed;boundary=efb8369b-607b-4dcf-9f92-e6cd8244db1e;charset=UTF-8' not supported]
[2022-05-10 11:31:52.206Z #dc2.024 DEBUG -            a17e] o.s.w.s.DispatcherServlet: Completed 415 UNSUPPORTED_MEDIA_TYPE

Debugging this, this is what I found:

When I send a PUT request to my endpoint described above, using the latest Spring versions, the enter image description here

This is the value of contentType:

enter image description here

So, what is happening is that each of these converters is returning false when asked:

canRead(targetClass=java.lang.Object, contentType=multipart/mixed; boundary=efb8369b-607b-4dcf-9f92-e6cd8244db1e;charset=UTF-8

This results in body not getting assgined a value, and a HttpMediaTypeNotSupportedException being thrown:

if (body == NO_VALUE) {
    if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
            (noContentType && !message.hasBody())) {
        return null;
    }
    throw new HttpMediaTypeNotSupportedException(contentType,
            getSupportedMediaTypes(targetClass != null ? targetClass : Object.class));
}

I tried to step through the same code using the older Spring versions listed in my original post (in order to see, for example, if the earlier Spring versions had more message converters):

<springVersion>5.0.16.RELEASE</springVersion>
<springBootVersion>2.0.5.RELEASE</springBootVersion>

but what I found was that this code that loops through the message converters is not executed using these older Spring versions

I am not sure what has changed between these Spring versions that this code is now being executed (and throwing an exception), where it was not before?

CodePudding user response:

See @wilkinsona answer in spring boot issue opened:

Spring Framework 5.1 also supports multipart PUT requests. This means that Spring MVC is now reading the body, making it unavailable to commons upload.

You should switch to using MVC's built-in multipart support. There are a few different ways to do that, for example:

@RestController
public class StagingController implements StagingApi
{    
    public ResponseEntity<Void> createOrReplaceBatch(@PathVariable("batchId") String batchId, MultipartHttpServletRequest request)
    {
        try {
            Collection<Part> parts = request.getParts();
  • Related