I have a method in jersey servlet which consumes both MULTIPART_FORM_DATA and application/x-www-form-urlencoded Media Types, In my request I am sending some parameters along with a file in the file input stream.
Here is my method
@POST
@Path("/upload")
@Consumes({MediaType.MULTIPART_FORM_DATA,MediaType.APPLICATION_FORM_URLENCODED})
@Produces(MediaType.TEXT_PLAIN)
public String uploadFile(MultivaluedMap<String,String> requestParamsPost,@FormDataParam("file") InputStream fis,
@FormDataParam("file") FormDataContentDisposition fdcd){
//some code goes here
}
But my problem is when I start my server after making the mapping of the servlet in web.xml, I get some severe exceptions
SEVERE: Missing dependency for method public javax.ws.rs.core.Response com.package.ImportService.uploadFile(java.lang.String,java.lang.String,java.lang.String) at parameter at index 0
SEVERE: Missing dependency for method public javax.ws.rs.core.Response com.package.ImportService.uploadFile(java.lang.String,java.lang.String,java.lang.String) at parameter at index 1
SEVERE: Missing dependency for method public javax.ws.rs.core.Response com.package.ImportService.uploadFile(java.lang.String,java.lang.String,java.lang.String) at parameter at index 2
Is it somehow possible to consume two Media Types in one method at single endpoint? Sending a file Parameter is necessary in every request?
The reason for the error is the MultivaluedMap
parameter. Jersey doesn't know what to do with it. You can only have one entity type per method. In your method you are trying to accept two different body types in the request. You can't do that. I don't even know how you plan on sending that from the client.
The application/x-www-form-urlencoded
data needs to be part of the multipart body. So you can do
@Consumes({MediaType.MULTIPART_FORM_DATA})
public String uploadFile(@FormDataParam("form-data") MultivaluedMap<String,String> form,
@FormDataParam("file") InputStream fis,
@FormDataParam("file") FormDataContentDisposition fdcd){
That would work. The only thing is, you need to make sure the client set the Content-Type
of the form-data
part to application/x-www-form-urlencoded
. If they don't, then the default Content-Type for that part will be text/plain
and Jersey will not be able to parse it to a MultivaluedMap
.
What you can do instead is just use FormDataBodyPart
as a method parameter, then explicitly set the media type. Then you can extract it to a MultivaluedMap
. This way the client doesn't need to be expected to set the Content-Type
for that part. Some clients don't even allow for setting individual part types.
Here's an example using Jersey Test Framework
import java.util.logging.Logger;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import static junit.framework.Assert.assertEquals;
public class MultipartTest extends JerseyTest {
@Path("test")
public static class MultiPartResource {
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response post(@FormDataParam("form-data") FormDataBodyPart bodyPart,
@FormDataParam("data") String data) {
bodyPart.setMediaType(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
MultivaluedMap<String, String> formData = bodyPart.getEntityAs(MultivaluedMap.class);
StringBuilder sb = new StringBuilder();
sb.append(data).append(";").append(formData.getFirst("key"));
return Response.ok(sb.toString()).build();
}
}
@Override
public ResourceConfig configure() {
return new ResourceConfig(MultiPartResource.class)
.register(MultiPartFeature.class)
.register(new LoggingFilter(Logger.getAnonymousLogger(), true));
}
@Override
public void configureClient(ClientConfig config) {
config.register(MultiPartFeature.class);
}
@Test
public void doit() {
FormDataMultiPart multiPart = new FormDataMultiPart();
multiPart.field("data", "hello");
multiPart.field("form-data", "key=world");
final Response response = target("test")
.request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA));
assertEquals("hello;world", response.readEntity(String.class));
}
}
If you look at the logging, you will see the request as
--Boundary_1_323823279_1458137333706
Content-Type: text/plain
Content-Disposition: form-data; name="data"
hello
--Boundary_1_323823279_1458137333706
Content-Type: text/plain
Content-Disposition: form-data; name="form-data"
key=world
--Boundary_1_323823279_1458137333706--
You can see the Content-Type
for the form-data
body part is text/plain
, which is the default, but in the server side, we explicitly set it before Jersey parses it
public Response post(@FormDataParam("form-data") FormDataBodyPart bodyPart,
@FormDataParam("data") String data) {
bodyPart.setMediaType(MediaType.APPLICATION_FORM_URLENCODED_TYPE);
MultivaluedMap<String, String> formData = bodyPart.getEntityAs(MultivaluedMap.class);