Friday, March 4, 2011

Jersey and Maven Share (i.e. Über Jars)

For the past year or so, much of the work I've done with Java has used Maven's Shade plugin. This plugin does a really excellent job of mashing dozens to hundreds of Java dependencies into a single jar file. This not only makes deployment easier, but for things like Hadoop jobs makes dependency management in general much easier.


Unfortunately, using the Shade plugin does occasionally have some negative side effects. Namely, on some occasions I've had jar files stomp on each with non-obvious outcomes. Today was one of those times.

The issue in this case only appeared in the production environment but not in test and not when running from my IDE under a debugger. The core service would start fine, but on any request, the code streaming data would throw an exception similar to:

javax.ws.rs.WebApplicationException
at com.sun.jersey.spi.container.ContainerResponse.write(ContainerResponse.java:240)
at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:593)
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:514)
at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:505)

This particular exception was particularly difficult to diagnose because Jersey writes a critical piece of data to stdout and not to the conventional logging infrastructure:

SEVERE: A message body writer for Java type, class java.lang.String, and MIME media type, application/octet-stream, was not found

When I finally realized this was the case, a Google search turned up this discussion where the difficult diagnostics had already been published by Gaëtan Sheridan . From there, it was a matter of excluding specific portions of the jersey-server plugin from the shade-generated artifact and all was well.

The relevant portion of the POM file looks like so:



<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>com.sun.jersey:jersey-server</artifact>
<excludes>
<exclude>META-INF/services/javax.ws.rs.ext.MessageBodyWriter</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>



Making that change fixed the issue.

The other painful reminder from this exercise was that common tools such as mvn jetty:run and launching from the IDE are no substitute for testing with the exact binaries and classpath used in production. Because my IDE and maven's jerry:run command manually assembled the classpath, it turns out that they did not experience the issue. Only when running directly from the über jar did the problem surface.