Jersey 2.12 – Web application deployment challenges and their solutions

September 5, 2014

This article describes some of the challenges encountered during deployment of our platform web applications and their solutions. The web applications under consideration use:

  1. Jersey (version 2.12) for REST-based web services framework.
  2. Spring (version 4.0.6) as dependency injection framework.
  3. Tomcat 8.0.11 as the application server.
  4. JDK 7 (version 7.0.45)

 

Jersey 2.12 which implements JAX-RS 2.0 API is the most recent release of Jersey. The web application works perfectly with Jersey 1.18 but refuses to start properly on Jersey 2.12. Here is what you need to do to get it working on Jersey 2.12.

 

Web application deployment on tomcat server

  • Unzip standard tomcat 8.0.11 binary distribution. Tomcat 8.0.11 supports Servlet Spec 3.1, JSP spec 2.3.
  • Build your project using maven. The following pom.xml captures all the required dependency for Jersey 2.12 and Spring 4.0.6:
<project xmlns="http://maven.apache.org/POM/4.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.junctiontv.app</groupId>
    <artifactId>sms-portal</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>sms-portal Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <properties>
    <spring.version>4.0.6.RELEASE</spring.version>
    <jersey.version>2.12</jersey.version>
    </properties>  
    <dependencies>
    <dependency>
          <groupId>asm</groupId>
          <artifactId>asm</artifactId>
    <version>3.3.1</version>
    </dependency>
    <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-core</artifactId>
         <version>${spring.version}</version>
    </dependency> 
    <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    </dependency>  
    <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-web</artifactId>
         <version>${spring.version}</version>
    </dependency>   
    <dependency>
         <groupId>org.glassfish.jersey.containers</groupId>
         <artifactId>jersey-container-servlet</artifactId>
         <version>${jersey.version}</version>
    </dependency>
    <!-- Required only when you are using JAX-RS Client -->
    <dependency>
         <groupId>org.glassfish.jersey.core</groupId>
         <artifactId>jersey-client</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <dependency>
         <groupId>org.glassfish.jersey.media</groupId>
         <artifactId>jersey-media-moxy</artifactId>
         <version>${jersey.version}</version>
    </dependency>    
    <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-api</artifactId>
         <version>1.7.2</version>
    </dependency>   
    <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-log4j12</artifactId>
         <version>1.7.2</version>
    </dependency>     
    <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>5.1.12</version>
    </dependency>    
    <dependency>
         <groupId>javax</groupId>
         <artifactId>javaee-api</artifactId>
         <version>7.0</version>
         <scope>provided</scope>
    </dependency>          
  </dependencies>
</project>

Note:
1. Dependency jersey-media-moxy is required for JSON binding and conversion.
2. Dependency javaee-api is required for java source compilation for cases where various javax.ws.* imports are used. However, jar is not required for deployment. The n ecessary classes are available in tomcat lib folder

• Deploy your app into TOMCAT_HOME/webapps copying the binary from MAVEN_PROJECT_HOME/target

The following section describes the changes and troubleshooting required to successfully launch the application.

web.xml changes for web application deployment
Jersey package has changed between version 1.x to 2.x. Accordingly, the following changes need to be done in web.xml for deployment.
• Change “servlet-class” com.sun.jersey.spi.container.servlet.ServletContainer (1.x) to org.glassfish.jersey.servlet.ServletContainer (2.x).
• Change “param-name” com.sun.jersey.config.property.packages (1.x) to jersey.config.server.provider.packages (2.x)
Following is a sample servlet config in web.xml

<servlet>  
    <servlet-name>SMS UI Service</servlet-name>
    <servlet-class> org.glassfish.jersey.servlet.ServletContainer </servlet-class>
    <init-param>
        <param-name>jersey.config.server.provider.packages</param-name>
        <param-value>com.junctiontv.sms.ui.rs</param-value>
    </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>SMS UI Service</servlet-name>
  <url-pattern>/**/**</url-pattern>
</servlet-mapping>


Runtime error on form submission using AJAX

If you are getting the following exception during runtime (check tomcat logs folder) and various AJAX data submission calls are failing, it is possibly happening because of strict HTTP header enforcement in Jersey 2.x:

org.apache.catalina.core.ApplicationContext.log Initializing Spring root WebApplicationContext
org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [SMS UI Service] in context with path [/sms] threw exception [java.lang.
IllegalStateException: 
The @FormParam is utilized when the content type of the request entity is not application/x-www-form-urlencoded] with root cause
 java.lang.IllegalStateException: The @FormParam is utilized when the content type of the request entity is not application/x-www-form-urlencoded
    at server.internal.inject.FormParamValueFactoryProvider$FormParamValueFactory.ensureValidRequest(FormParamValueFactoryProvider.java:176)
    at  fish.jersey.server.internal.inject.FormParamValueFactoryProvider$FormParamValueFactory.getForm(FormParamValueFactoryProvider.java:161)
    at ssfish.jersey.server.internal.inject.FormParamValueFactoryProvider$FormParamValueFactory.provide(FormParamValueFactoryProvider.java:116)
    at org.glassfish.jersey.server.spi.internal.ParameterValueHelper.getParameterValues(ParameterValueHelper.java:81)
    at rnal.JavaResourceMethodDispatcherProvider$AbstractMethodParamInvoker.getParamValues(JavaResourceMethodDispatcherProvider.java:121)
    at y.server.model.internal.JavaResourceMethodDispatcherProvider$TypeOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:195)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:104)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:387)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:331)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:103)
    at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:271)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:297)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:254)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1030)
    at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:373)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:381)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:344)
    at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:221)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:301)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:74)
    at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:509)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1015)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:651)
    at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1575)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1533)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
    at java.lang.Thread.run(Unknown Source)

To solve this problem, a minor tweak is required in the web application. If one has a REST method definition like

@POST
@Path("/delete")
@Produces("application/json")
public String delete(@FormParam("id")String id){

Since @Consumes() is not defined by the default delete method Consumes(application/x-www-form-urlencoded), then Ajax call invoked from the javascript code should look like:

jQuery.ajax({
            url : '/register',
            type : 'POST',
            data:obj,
            //contentType: "application/ x-www-form-urlencoded",
            async : true,
            success : function(response, textStatus) {            },
            error : function(XMLHttpRequest, textStatus, errorThrown) {   }
        	 });

Comment out the contentType (default is x-www-form-urlencoded) or ensure contentType is send as x-www-form-urlencoded. That should solve this problem!