By Sambit


2014-12-22 19:59:14 8 Comments

I want to create an employee information in the system by uploading an image along with employee data. I am able to do it with different rest calls using jersey. But I want to achieve in one rest call. I provide below the structure. Please help me how to do in this regard.

@POST
@Path("/upload2")
@Consumes({MediaType.MULTIPART_FORM_DATA,MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response uploadFileWithData(
        @FormDataParam("file") InputStream fileInputStream,
        @FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
        Employee emp) {

//..... business login

}

Whenever I am trying to do, I get error in Chrome postman. The simple structure of my Employee json is given below.

{
    "Name": "John",
    "Age": 23,
    "Email": "[email protected]",
    "Adrs": {
        "DoorNo": "12-A",
        "Street": "Street-11",
        "City": "Bangalore",
        "Country": "Karnataka"
    }
}

However I can do it by making two different call, but I want to achieve in one rest call so that I can receive the file as well as the actual data of the employee.

Request you to help in this regard.

5 comments

@Paul Samsotha 2014-12-23 05:09:12

You can't have two Content-Types (well technically that's what we're doing below, but they are separated with each part of the multipart, but the main type is multipart). That's basically what you are expecting with your method. You are expecting mutlipart and json together as the main media type. The Employee data needs to be part of the multipart. So you can add a @FormDataParam("emp") for the Employee.

@FormDataParam("emp") Employee emp) { ...

Here's the class I used for testing

@Path("/multipart")
public class MultipartResource {

    @POST
    @Path("/upload2")
    @Consumes({MediaType.MULTIPART_FORM_DATA})
    public Response uploadFileWithData(
            @FormDataParam("file") InputStream fileInputStream,
            @FormDataParam("file") FormDataContentDisposition cdh,
            @FormDataParam("emp") Employee emp) throws Exception{

        Image img = ImageIO.read(fileInputStream);
        JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(img)));
        System.out.println(cdh.getName());
        System.out.println(emp);

        return Response.ok("Cool Tools!").build();
    } 
}

First I just tested with the client API to make sure it works

@Test
public void testGetIt() throws Exception {

    final Client client = ClientBuilder.newBuilder()
        .register(MultiPartFeature.class)
        .build();
    WebTarget t = client.target(Main.BASE_URI).path("multipart").path("upload2");

    FileDataBodyPart filePart = new FileDataBodyPart("file", 
                                             new File("stackoverflow.png"));
    // UPDATE: just tested again, and the below code is not needed.
    // It's redundant. Using the FileDataBodyPart already sets the
    // Content-Disposition information
    filePart.setContentDisposition(
            FormDataContentDisposition.name("file")
                                    .fileName("stackoverflow.png").build());

    String empPartJson
            = "{"
            + "  \"id\": 1234,"
            + "  \"name\": \"Peeskillet\""
            + "}";

    MultiPart multipartEntity = new FormDataMultiPart()
            .field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)
            .bodyPart(filePart);

    Response response = t.request().post(
            Entity.entity(multipartEntity, multipartEntity.getMediaType()));
    System.out.println(response.getStatus());
    System.out.println(response.readEntity(String.class));

    response.close();
}

I just created a simple Employee class with an id and name field for testing. This works perfectly fine. It shows the image, prints the content disposition, and prints the Employee object.

I'm not too familiar with Postman, so I saved that testing for last :-)

enter image description here

It appears to work fine also, as you can see the response "Cool Tools". But if we look at the printed Employee data, we'll see that it's null. Which is weird because with the client API it worked fine.

If we look at the Preview window, we'll see the problem

enter image description here

There's no Content-Type header for the emp body part. You can see in the client API I explicitly set it

MultiPart multipartEntity = new FormDataMultiPart()
        .field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)
        .bodyPart(filePart);

So I guess this is really only part of a full answer. Like I said, I am not familiar with Postman So I don't know how to set Content-Types for individual body parts. The image/png for the image was automatically set for me for the image part (I guess it was just determined by the file extension). If you can figure this out, then the problem should be solved. Please, if you find out how to do this, post it as an answer.


And just for completeness...

Basic configurations:

Dependency:

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

Client config:

final Client client = ClientBuilder.newBuilder()
    .register(MultiPartFeature.class)
    .build();

Server config:

// Create JAX-RS application.
final Application application = new ResourceConfig()
    .packages("org.glassfish.jersey.examples.multipart")
    .register(MultiPartFeature.class);

UPDATE

So as you can see from the Postman client, some clients are unable to set individual parts' Content-Type, this includes the browser, in regards to it's default capabilities when using FormData (js).

We can't expect the client to find away around this, so what we can do, is when receiving the data, explicitly set the Content-Type before deserializing. For example

@POST
@Path("upload2")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFileAndJSON(@FormDataParam("emp") FormDataBodyPart jsonPart,
                                  @FormDataParam("file") FormDataBodyPart bodyPart) { 
     jsonPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
     Employee emp = jsonPart.getValueAs(Employee.class);
}

It's a little extra work to get the POJO, but it is a better solution than forcing the client to try and find it's own solution.


Asides

  • There is a conversation in these comments that you may be interested in if you are using a different Connector than the default HttpUrlConnection.

@AbuMariam 2015-02-26 19:24:19

Hi, may I please trouble you (or any other kind soul who reads this and knows) to update the code in the client API. For example the first line has "c.target" but what is c here?

@Paul Samsotha 2015-02-26 22:54:06

@user3223841 See "Client Config" at the very bottom. c == client. And I updated the code. Thank you.

@gkiko 2015-06-29 12:48:19

Have you tested your code on large files? I get same error on form submission

@cen 2016-04-07 15:41:25

I would give you a 100 upvotes if I could. This is what a well researched, up to date, useful, with workarounds answer is meant to be!

@Geo Thomas 2016-09-01 04:25:43

@peeskillet, I have a similar situation and I am new to these, could you please help me if you could stackoverflow.com/questions/39177212/…

@Manuel Sopena Ballesteros 2017-01-25 09:32:52

fantastic answer!

@AlikElzin-kilaka 2017-03-27 07:42:03

Any idea how to test it using curl?

@Paul Samsotha 2017-03-27 07:52:18

@AlikElzin-kilaka curl -F [email protected] -F [email protected];type=application/json -i <url>

@Azamat Almukhametov 2017-04-17 13:54:12

Dear @peeskillet, great job! Could you tell how should we handle FormDataBodyPart bodyBart? I'd like to get stream of file.. Thanks.

@Paul Samsotha 2017-04-17 13:56:29

@AzamatAlmukhametov for the file part, using the FormDataBodyPart wasn't needed. I think I was just copy and pasting. You could leave it as an InputStream parameter. Only the FormDataBodyPart is needed for the JSON part because we need to change the media type. But for the file, we can just get the raw input stream. Another way is to use bodyPart.getValueAs(InputStream.class), but it's not needed. Just make the parameter an InputStream

@user2098324 2017-04-19 14:32:38

this is nice that we should do deserialisation at server side and create POJO but would it run validations like @NotNull of POJO classes ?

@Paul Samsotha 2017-04-19 14:40:18

@user2098324 It won't. If this is something you need, don't use JSON. Just use individual simple valued mutlipart fields. You can use a @BeanParam as mentioned here to combine all the fields into a bean. You can use @FormDataParams in the bean. If you do this, then the bean validation would work

@zameer 2015-10-18 14:08:13

You can access the Image File and data from a form using MULTIPART FORM DATA By using the below code.

@POST
@Path("/UpdateProfile")
@Consumes(value={MediaType.APPLICATION_JSON,MediaType.MULTIPART_FORM_DATA})
@Produces(value={MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public Response updateProfile(
    @FormDataParam("file") InputStream fileInputStream,
    @FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
    @FormDataParam("ProfileInfo") String ProfileInfo,
    @FormDataParam("registrationId") String registrationId) {

    String filePath= "/filepath/"+contentDispositionHeader.getFileName();

    OutputStream outputStream = null;
    try {
        int read = 0;
        byte[] bytes = new byte[1024];
        outputStream = new FileOutputStream(new File(filePath));

        while ((read = fileInputStream.read(bytes)) != -1) {
            outputStream.write(bytes, 0, read);
        }

        outputStream.flush();
        outputStream.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (outputStream != null) { 
            try {
                outputStream.close();
            } catch(Exception ex) {}
        }
    }
}

@mkag 2017-02-03 09:25:39

I want add a comment on peeskillet but don't have 50 reputation points, hence adding as an answer:

When I tried @peeskillet solution with Jersey client 2.21.1, there was 400 error. It worked when I added following in my client code:

  MediaType contentType = MediaType.MULTIPART_FORM_DATA_TYPE;
  contentType = Boundary.addBoundary(contentType);

  Response response = t.request().post(
        Entity.entity(multipartEntity, contentType));

instead of hardcoded MediaType.MULTIPART_FORM_DATA in post request call.

@Paul Samsotha 2018-05-25 07:35:36

You don't need to manually create the boundary. You can just use multipartEntity.getMediaType(). I originally was using MediaType.MULTIPART_FORM_DATA which did not add the boundary. But when you use the getMediaType() method of the MutliPart, it will have the boundary.

@Ben 2018-11-14 12:43:03

@PaulSamsotha if you use another connector (i.e. apache instead of jdk) than there will be not boundary generated for you.

@Paul Samsotha 2018-11-14 14:58:54

@Ben You're right. Somewhere in the docs, it says this about the different connectors, where the WriterInterceptors and MessageBodyWriters are not able to change outbound headers. This would be required for adding the boundary. See the warning here in the Client docs.

@Ben 2018-11-15 14:09:40

So either automatic boundary generation, but no chunked uploads (HttpUrlConnection is bogus in this regard) or manual boundary generation. I chose the latter, because buffering a 4 GiB file without warning the user is a no-go. I created an issue on their github tracker.

@Raman B 2016-07-20 06:37:00

I used file upload example from,

http://www.mkyong.com/webservices/jax-rs/file-upload-example-in-jersey/

in my resource class i have below method

@POST
    @Path("/upload")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response  attachupload(@FormDataParam("file") byte[] is,
@FormDataParam("file") FormDataContentDisposition fileDetail,
@FormDataParam("fileName") String flename){
attachService.saveAttachment(flename,is);
}

in my attachService.java i have below method

 public void saveAttachment(String flename,  byte[] is) {
            // TODO Auto-generated method stub
         attachmentDao.saveAttachment(flename,is);

        }

in Dao i have

attach.setData(is);
attach.setFileName(flename);

in my HBM mapping is like

<property name="data" type="binary" >
            <column name="data" />
</property>

This working for all type of files like .PDF,.TXT, .PNG etc.,

@Mário Carvalho 2017-01-23 18:01:17

YOU JUST SAVED MY LIFE with the byte[] paramater instead of InputStream... @Ramam I owe you a beer!!

@pitaside 2016-01-04 18:08:44

Your ApplicationConfig should register the MultiPartFeature.class from the glassfish.jersey.media.. so as to enable file upload

@javax.ws.rs.ApplicationPath(ResourcePath.API_ROOT)
public class ApplicationConfig extends ResourceConfig {  
public ApplicationConfig() {
        //register the necessary headers files needed from client
        register(CORSConfigurationFilter.class);
        //The jackson feature and provider is used for object serialization
        //between client and server objects in to a json
        register(JacksonFeature.class);
        register(JacksonProvider.class);
        //Glassfish multipart file uploader feature
        register(MultiPartFeature.class);
        //inject and registered all resources class using the package
        //not to be tempered with
        packages("com.flexisaf.safhrms.client.resources");
        register(RESTRequestFilter.class);
    }

Related Questions

Sponsored Content

0 Answered Questions

Grails JAX-RS REST multipart file upload

0 Answered Questions

4 Answered Questions

1 Answered Questions

1 Answered Questions

0 Answered Questions

Upload file to Jersey web services with spring MultipartFile

2 Answered Questions

1 Answered Questions

3 Answered Questions

[SOLVED] How to make post ajax call with JSON data to Jersey rest service?

1 Answered Questions

[SOLVED] File upload with ExtJS and Jersey

Sponsored Content