Search code examples
springmultipartform-datajersey-2.0dropzone.jsjersey-client

Web Application Exception: javax.ws.rs.NotSupportedException: HTTP 415 Unsupported Media Type


I'm using jersey and spring in my application. I don't have any issue when working with any AJAX requests but i get media not supported error when uploading files. I tried several ways, but haven't find any fix for it. Wondering if anyone can help me find the root cause of the issue. Appreciate your help.

Server side implemention

web.xml

<servlet>
    <servlet-name>jersey-servlet</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>javax.ws.rs.Application</param-name>
        <param-value>org.blanc.whiteboard.RestResourceApplication</param-value>
    </init-param>
    <init-param>
        <param-name>org.glassfish.jersey.api.json.POJOMappingFeature</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>org.glassfish.jersey.filter.LoggingFilter;org.glassfish.jersey.media.multipart.MultiPartFeature</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

resource class

@Path("/upload")
@POST
//@Consumes({ MediaType.MULTIPART_FORM_DATA })
public Response uploadAsset(@Context SecurityContext sc,
        MultipartHttpServletRequest request) {
    User user = loadUserFromSecurityContext(sc);
    MultipartFile file = null;
    // 1. build an iterator
    Iterator<String> itr = request.getFileNames();
    // 2. get each file
    while (itr.hasNext()) {
        // 2.1 get next MultipartFile
        file = request.getFile(itr.next());
        if (file == null) {
            throw new FileUploadException("File is null.");
        }
        LOG.info(file.getOriginalFilename() + " uploaded! " + files.size());
        // 2.2 if files > 10 remove the first from list
        if (files.size() > 10)
            files.pop();

    }
    return Response.ok().build();
}

pom.xml

    <jersey.version>2.20</jersey.version>
   .....
        <dependency>
        <groupId>org.glassfish.jersey.ext</groupId>
        <artifactId>jersey-spring3</artifactId>
        <version>${jersey.version}</version>
        <exclusions>
            <exclusion>
                <artifactId>spring-core</artifactId>
                <groupId>org.springframework</groupId>
            </exclusion>
            <exclusion>
                <artifactId>spring-aop</artifactId>
                <groupId>org.springframework</groupId>
            </exclusion>
            <exclusion>
                <artifactId>spring-beans</artifactId>
                <groupId>org.springframework</groupId>
            </exclusion>
            <exclusion>
                <artifactId>spring-context</artifactId>
                <groupId>org.springframework</groupId>
            </exclusion>
            <exclusion>
                <artifactId>spring-web</artifactId>
                <groupId>org.springframework</groupId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-client</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-common</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-json-jackson</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <!-- File upload -->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>${commons-fileupload.version}</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>${commons-io.version}</version>
    </dependency>

Error :

Aug 12, 2015 7:21:58 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 4 * Server responded with a response on thread http-bio-8080-exec-5
4 < 200
4 < Access-Control-Allow-Headers: X-HTTP-Method-Override, Content-Type, x-requested-with, Authorization, Cache-Control
4 < Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
4 < Access-Control-Allow-Origin: http://localhost:9000
4 < Access-Control-Max-Age: 3600
4 < Content-Type: application/json

Aug 12, 2015 7:22:15 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 5 * Server has received a request on thread http-bio-8080-exec-1
5 > OPTIONS http://localhost:8080/v1.0/assets/upload
5 > accept: */*
5 > accept-encoding: gzip, deflate, sdch
5 > accept-language: en-US,en;q=0.8
5 > access-control-request-headers: accept, cache-control, content-type, x-requested-with
5 > access-control-request-method: POST
5 > cache-control: no-cache
5 > connection: keep-alive
5 > host: localhost:8080
5 > origin: http://localhost:9000
5 > pragma: no-cache
5 > referer: http://localhost:9000/debug.html
5 > user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36

Aug 12, 2015 7:22:15 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 5 * Server responded with a response on thread http-bio-8080-exec-1
5 < 200
5 < Access-Control-Allow-Headers: X-HTTP-Method-Override, Content-Type, x-requested-with, Authorization, Cache-Control
5 < Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
5 < Access-Control-Allow-Origin: http://localhost:9000
5 < Access-Control-Max-Age: 3600
5 < Allow: POST,OPTIONS
5 < Content-Type: application/vnd.sun.wadl+xml
5 < Last-modified: Wed, 12 Aug 2015 19:22:15 EDT

Aug 12, 2015 7:22:15 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 6 * Server has received a request on thread http-bio-8080-exec-10
6 > POST http://localhost:8080/v1.0/assets/upload
6 > accept: application/json
6 > accept-encoding: gzip, deflate
6 > accept-language: en-US,en;q=0.8
6 > cache-control: no-cache
6 > connection: keep-alive
6 > content-length: 108054
6 > content-type: multipart/form-data; boundary=----WebKitFormBoundarywS3v9iwIqD3NwnYt
6 > host: localhost:8080
6 > origin: http://localhost:9000
6 > pragma: no-cache
6 > referer: http://localhost:9000/debug.html
6 > user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36
6 > x-requested-with: XMLHttpRequest

19:22:15.340 [http-bio-8080-exec-10] INFO  o.b.w.r.GenericExceptionMapper - Web Application Exception: **javax.ws.rs.NotSupportedException: HTTP 415 Unsupported Media Type**
Aug 12, 2015 7:22:15 PM org.glassfish.jersey.filter.LoggingFilter log
INFO: 6 * Server responded with a response on thread http-bio-8080-exec-10
6 < 415
6 < Access-Control-Allow-Headers: X-HTTP-Method-Override, Content-Type, x-requested-with, Authorization, Cache-Control
6 < Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
6 < Access-Control-Allow-Origin: http://localhost:9000
6 < Access-Control-Max-Age: 3600

On front end i'm using [dropzone][1]


Solution

  • You can't just expect the request body to be parsed into arbitrary data types (i.e. MultipartHttpServletRequest). How it works (with Jersey and JAX-RS) is through MessageBodyReaders (you can read more here).

    Basically, based on the Content-Type of the request (which is multipart/form-data), and the method parameter type (MultipartHttpServletRequest), Jersey will look through it's registry for a MessageBodyReader that can handle those two factors. If it can't find one, it will throw the exception you are facing and send s 415.

    That being said, what you should do is just use the multipart support already provided by Jersey. It comes with the needed MessageBodyReader to handle some Jersey specific (and other common) data types for multipart data. You first need the dependency

    <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-multipart</artifactId>
        <version>${jersey2.version}</version>
    </dependency>
    

    Then you need to register the feature, which you've already done by adding the feature classname in your init-param

    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>
            org.glassfish.jersey.filter.LoggingFilter;
            org.glassfish.jersey.media.multipart.MultiPartFeature
        </param-value>
    </init-param>
    

    Then you just need to use it's components. The link I provided is give a full explanation. The most common way to use this feature is to use the @FormDataParam annotation for your method parameters. For example

    @Consumes({ MediaType.MULTIPART_FORM_DATA })
    public Response uploadAsset(@FormDataParam("file") InputStream in) {}
    

    where the InputStream is a particular file with the name "file" in the Content-Disposition.

    There are a few other ways to accept files. You can include the entire multipart body, and just iterate through the parts, like you are currently doing. For example

    @Consumes({ MediaType.MULTIPART_FORM_DATA })
    public Response uploadAsset(FormDataMultiPart multipart) {
        Map<String, List<FormDataBodyPart>> map = multipart.getFields();
        for (Map.Entry<String, List<FormDataBodyPart>> entry: map.entrySet()) {
            for (FormDataBodyPart part: entry.getValue()) {
                InputStream in = part.getEntityAs(InputStream.class);
                String name = part.getName();
            }
        }
    }
    

    For more information, visit the link I provided above.