Woman coding in JavaIn this article we will see how to create a self-contained Java servlet as runnable jar (a fat jar). This jar file can then be moved to any platform with a JVM and run with a simple java -jar myServlet.jar. There is no need to setup or configure anything else!

Creating a runnable server jar can be a great option if you want an easily portable and deployable app. This may not be a good option if your app is composed of several servlets, because each one will have a duplicate server embedded.

Some frameworks can do this for you automatically, like Spring Boot. But if you are not using such a framework, or don't want to use those features of it, here is how to do it manually. We will use Spring web-mvc (without Boot) to illustrate the concept. The same principles apply to any other framework as well.

First, let's setup our Maven dependencies. We will only have two for this project. One for embedded Tomcat, and one for Spring Web MVC:

8.5.235.0.2.RELEASE
...
org.apache.tomcat.embedtomcat-embed-core${tomcat.version}org.springframeworkspring-webmvc${spring.version}commons-loggingcommons-logging

Next we need to create a launcher class with a main method that will serve as our JAR entry point, and start Tomcat (code below is based on this nice example from Heroku):

public class Launch {
 public static void main(String[] args) throws Exception {

 //String webappDirLocation = "src/main/webapp/";
 String webappDirLocation = "./";
 Tomcat tomcat = new Tomcat();

 //The port that we should run on can be set into an environment variable
 //Look for that variable and default to 8080 if it isn't there.
 String webPort = System.getenv("PORT");
 if(webPort == null || webPort.isEmpty()) {
 webPort = "8080";
 }
 System.out.println("configuring app with basedir: " + new File("./" + webappDirLocation).getAbsolutePath());

 // If you want to declare an alternative location for your "WEB-INF/classes" dir
 // (Servlet 3.0 annotation will also work) 
 /*
 File additionWebInfClasses = new File("target/classes");
 WebResourceRoot resources = new StandardRoot(ctx);
 resources.addPreResources(new DirResourceSet(resources, "/WEB-INF/classes",
 additionWebInfClasses.getAbsolutePath(), "/"));
 ctx.setResources(resources);
 */

 tomcat.start();
 tomcat.getServer().await();
 }
}

Now that we have a Tomcat instance up and running, the next step is to tell it about our servlet. This would traditionally be done in web.xml and a context.xml file. Here we will use Java configuration to replace those. We will implement Spring's WebAppInitializer interface to configure our servlet:

public class WebAppInitializer implements WebApplicationInitializer {
 @Override
 public void onStartup(ServletContext container) {
 // Create the 'root' Spring application context
 // These lines (along with AppConfig.java) replace the context config xml file,
 // and the contextConfigLocation init-param pointing to it in web.xml
 AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
 rootContext.register(AppConfig.class);
 container.addListener(new ContextLoaderListener(rootContext));

 // Create the dispatcher servlet's Spring application context
 AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();

 // Register and map the dispatcher servlet
 // replaces the servlet-name, servlet-class and servlet-mapping in web.xml
 ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
 dispatcher.setLoadOnStartup(1);
 dispatcher.addMapping("/");
 }
}

How does this work, and where does the container come from? Spring's SpringServletContainerInitializer will automatically detect any implementation of WebApplicationInitializer and use it, providing the servlet context. SpringServletContainerInitializer is able to do that because it implements ServletContainerInitializer from javax.servlet. In Servlet 3.0, any implementation of that interface automatically gets loaded and is given the container by the server itself, which is Tomcat in this case.

The initializer requires a class to help it configure the servlet. This can be an empty class with some Spring annotations, namely: @Configuration, @EnableWebMvc, and @ComponentScan.

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.captech.embeddedServerExample"})
public class AppConfig implements WebMvcConfigurer {
}

Next you can create a controller by annotating a POJO.

@RestController
public class HelloController {

 @GetMapping("/hello")
 public String sayHello(){
 return "Hello, World!\n";
 }

}

That's it for the code. But to create the single runnable jar, we need an extra maven plugin. This is easy to do with the maven-shade plugin. Here is the pom config for it:

org.apache.maven.pluginsmaven-shade-pluginshadetruetechChallenge-fatcom.captech.embeddedServerExample.Launch

Run mvn package and it will produce your jar. Run it from your IDE or at the command line with java -jar target/embeddedServerExample-fat.jar.

Test it:

> curl http://localhost:8080/hello
Hello, World!
To recap, the key points here are:
  • The server needs to use the embedded version of tomcat.
  • The servlet can be completely configured in Java code.
  • The whole thing can be packaged in one easy to deploy JAR that does not require external configuration.

The full code for this example is on Github here.