[java] File upload along with other object in Jersey restful web service

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.

This question is related to java jersey jax-rs multipartform-data postman

The answer is


When I tried @PaulSamsotha's 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.

The reason this is needed is because when you use a different Connector (like Apache) for the Jersey Client, it is unable to alter outbound headers, which is required to add a boundary to the Content-Type. This limitation is explained in the Jersey Client docs. So if you want to use a different Connector, then you need to manually create the boundary.


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.,


The request type is multipart/form-data and what you are sending is essentially form fields that go out as bytes with content boundaries separating different form fields.To send an object representation as form field (string), you can send a serialized form from the client that you can then deserialize on the server.

After all no programming environment object is actually ever traveling on the wire. The programming environment on both side are just doing automatic serialization and deserialization that you can also do. That is the cleanest and programming environment quirks free way to do it.

As an example, here is a javascript client posting to a Jersey example service,

submitFile(){

    let data = new FormData();
    let account = {
        "name": "test account",
        "location": "Bangalore"
    }

    data.append('file', this.file);
    data.append("accountKey", "44c85e59-afed-4fb2-884d-b3d85b051c44");
    data.append("device", "test001");
    data.append("account", JSON.stringify(account));
    
    let url = "http://localhost:9090/sensordb/test/file/multipart/upload";

    let config = {
        headers: {
            'Content-Type': 'multipart/form-data'
        }
    }

    axios.post(url, data, config).then(function(data){
        console.log('SUCCESS!!');
        console.log(data.data);
    }).catch(function(){
        console.log('FAILURE!!');
    });
},

Here the client is sending a file, 2 form fields (strings) and an account object that has been stringified for transport. here is how the form fields look on the wire,

enter image description here

On the server, you can just deserialize the form fields the way you see fit. To finish this trivial example,

    @POST
@Path("/file/multipart/upload")
@Consumes({MediaType.MULTIPART_FORM_DATA})
public Response uploadMultiPart(@Context ContainerRequestContext requestContext,
        @FormDataParam("file") InputStream fileInputStream,
        @FormDataParam("file") FormDataContentDisposition cdh,
        @FormDataParam("accountKey") String accountKey,
        @FormDataParam("account") String json)  {
    

    
    System.out.println(cdh.getFileName());
    System.out.println(cdh.getName());
    System.out.println(accountKey);
    
    try {
        Account account = Account.deserialize(json);
        System.out.println(account.getLocation());
        System.out.println(account.getName());
        
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    return Response.ok().build();
    
}

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.

See UPDATE below for solution


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);

If you're having problems with the server configuration, one of the following posts might help


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.

Another option is to use a String parameter and use whatever JSON library you use to deserialze the String to the POJO (like Jackson ObjectMapper). With the previous option, we just let Jersey handle the deserialization, and it will use the same JSON library it uses for all the other JSON endpoints (which might be preferred).


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.

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) {}
        }
    }
}

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);
    }

Examples related to java

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How much should a function trust another function How to implement a simple scenario the OO way Two constructors How do I get some variable from another class in Java? this in equals method How to split a string in two and store it in a field How to do perspective fixing? String index out of range: 4 My eclipse won't open, i download the bundle pack it keeps saying error log

Examples related to jersey

How to download a file using a Java REST service and a data stream File upload along with other object in Jersey restful web service MessageBodyWriter not found for media type=application/json org.glassfish.jersey.servlet.ServletContainer ClassNotFoundException How to add Headers on RESTful call using Jersey Client API java.lang.ClassNotFoundException: com.sun.jersey.spi.container.servlet.ServletContainer Dependency injection with Jersey 2.0 How to send and receive JSON data from a restful webservice using Jersey API Jersey client: How to add a list as query parameter @POST in RESTful web service

Examples related to jax-rs

Java 8 LocalDate Jackson format File upload along with other object in Jersey restful web service Best practice for REST token-based authentication with JAX-RS and Jersey MessageBodyWriter not found for media type=application/json Can not deserialize instance of java.util.ArrayList out of START_OBJECT token Read response body in JAX-RS client from a post request What is the difference between JAX-RS and JAX-WS? javax.xml.bind.JAXBException: Class *** nor any of its super class is known to this context When to use @QueryParam vs @PathParam How to set up JAX-RS Application using annotations only (no web.xml)?

Examples related to multipartform-data

How do I set multipart in axios with react? Send FormData with other field in AngularJS Send multipart/form-data files with angular using $http How to set up a Web API controller for multipart/form-data Upload a file to Amazon S3 with NodeJS File upload along with other object in Jersey restful web service Uploading file using POST request in Node.js Angularjs how to upload multipart form data and a file? Convert JS Object to form data Posting raw image data as multipart/form-data in curl

Examples related to postman

Converting a POSTMAN request to Curl "Could not get any response" response when using postman with subdomain How do I format {{$timestamp}} as MM/DD/YYYY in Postman? How do I POST XML data to a webservice with Postman? How to send Basic Auth with axios How to install/start Postman native v4.10.3 on Ubuntu 16.04 LTS 64-bit? Websocket connections with Postman FromBody string parameter is giving null "Post Image data using POSTMAN" How to import Swagger APIs into Postman?