You can easily find a lot of articles on the web describing the process to use Jersey client with a Jersey 1.x Server to do multi-part uploads. However, when trying to use Apache HTTP client, it uncovers a bug in jersey causing a NullPointerException – https://java.net/jira/browse/JERSEY-1658
SEVERE: The RuntimeException could not be mapped to a response, re-throwing to the HTTP container java.lang.NullPointerException at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.unquoteMediaTypeParameters(MultiPartReaderClientSide.java:227) at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readMultiPart(MultiPartReaderClientSide.java:154) at com.sun.jersey.multipart.impl.MultiPartReaderServerSide.readMultiPart(MultiPartReaderServerSide.java:80) at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:144) at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:82) at com.sun.jersey.spi.container.ContainerRequest.getEntity(ContainerRequest.java:488) at com.sun.jersey.server.impl.model.method.dispatch.EntityParamDispatchProvider$EntityInjectable.getValue(EntityParamDispatchProvider.java:123) at com.sun.jersey.server.impl.inject.InjectableValuesProvider.getInjectableValues(InjectableValuesProvider.java:46) at com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$EntityParamInInvoker.getParams(AbstractResourceMethodDispatchProvider.java:153) at com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$ResponseOutInvoker._dispatch(AbstractResourceMethodDispatchProvider.java:203) at com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher.dispatch(ResourceJavaMethodDispatcher.java:75)
Here’s the relevant piece of code from Jersey
protected static MediaType unquoteMediaTypeParameters(final MediaType mediaType, final String... parameters) { 235 if (parameters == null || parameters.length == 0) { 236 return mediaType; 237 } 238 239 final HashMap unquotedParams = new HashMap(mediaType.getParameters()); 240 241 for (final String parameterName : parameters) { 242 String parameterValue = mediaType.getParameters().get(parameterName); 243 244 if (parameterValue.startsWith("\"")) { 245 parameterValue = parameterValue.substring(1, parameterValue.length() - 1); 246 unquotedParams.put(parameterName, parameterValue); 247 } 248 } 249 250 return new MediaType(mediaType.getType(), mediaType.getSubtype(), unquotedParams); 251 }
The error occurs because Jersey Server expects the boundary parameter be set as a part of the content-type header, which is not being set by Apache HTTP Client. It can be verified by looking at the request made by Jersey client vs Apache client
Jersey Client
Content-Type=multipart/form-data;boundary=Boundary-1234567890
Apache HTTP Client
Content-Type=multipart/form-data
And since the boundary parameter is missing, it ends up throwing a NPE.
SOLUTION
I was able manually hack in the boundary parameter into the Content-Type header of the request making it available for Jersey parser and thus avoiding the NPE. The issue with this fix however is that the class MultipartFormEntity is package private and therefore, the utility class described below needs to be created in the package org.apache.http.entity.mime
package org.apache.http.entity.mime; import org.apache.commons.lang3.Validate; import org.apache.http.HttpEntity; public class MultiPartEntityUtil { public static String getBoundaryValue(HttpEntity entity) { Validate.notNull(entity); if( entity instanceof MultipartFormEntity ) { MultipartFormEntity formEntity = (MultipartFormEntity)entity; AbstractMultipartForm form = formEntity.getMultipart(); Validate.notNull(form); return form.getBoundary(); } throw new IllegalArgumentException("Provided entity is of type: " + entity.getClass() + " instead of expected: MultipartFormEntity"); } }
With this utility class, we can simply set the Content-Type header as follows
MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); for (File file : files) { builder.addBinaryBody(file.getName(), file, ContentType.DEFAULT_BINARY, file.getName()); } HttpEntity entity = builder.build(); String boundary= MultiPartEntityUtil.getBoundaryValue(entity); ... request.addHeader(HttpHeaders.CONTENT_TYPE, "multipart/form-data;boundary="+boundary);
This hack makes sure that Jersey server finds the appropriate boundary parameter. Now you can successfully do multipart uploads with Apache client on Jersey 1.x