Apache Tomcat Logo
Apache Tomcat Logo

A number of Java web applications and services, both open source and commercial (Alfresco, iRise, Confluence, etc.), tend to embed the entire Apache Tomcat servlet engine in their distribution packages. Atlatisan has even gone as far as only supporting their embedded Tomcat package1, no longer offering a WAR/EAR file distribution. These packages contain the full Tomcat engine and configuration files and seem really overkill. In most configurations, the default setting files are never even changed. Surely there must be a way to launch Tomcat in code and only require the tomcat jars as dependencies? In the following tutorial, we’ll examine Jetty, an embedded servlet engine designed for this purpose, and show how to replicate the Jetty setup with Tomcat.

I started looking at embedding Tomcat for BigSense, an open source web service I created for monitoring sensor networks. I wanted to get to the point where I could distribute it as a standard Linux package, which would start a service, without being dependent on a Tomcat package. The following examples are all in Scala, as that’s what BigSense is written in, but you can easily translate all the source code and concepts to Java as well.

First, I created a trait (which is similar to an interface for you Java people) that had a couple of simple functions for starting and stopping my web server. I also pull a port number from a configuration file. This is the only configurable part of my implementation, but you may also want to add configuration for context paths.

package io.bigsense.server

/**
 * Created by sumit on 4/28/14.
 */
trait ServerTrait {

  lazy val httpPort = try {
    BigSenseServer.config.options("httpPort").toInt
  }
  catch {
    case e: NumberFormatException => Exit.invalidHttpPort
    0 //makes compiler happy
  }

  def startServer()
  def stopServer()

}

The following is my Jetty implementation in Scala. Most of this was taken directly from the official Jetty documentation. All of my static content (images, css and javascript) is packaged directly into the jar file and can be accessed as class path resources. If you’re using a build tool like SBT, Gradel or Maven, you should be able to place these files in src/main/resources in your project folder. Jetty’s WebAppContext allows for calling setResourceBase for serving up static resources from your project. This example also shows adding a Servlet with a given context path (in this case, I only have one servlet, mapped to the root and named MasterServlet). You can also see an EventListener example as well. Most of the configuration in a standard web.xml can be set in code with Jetty.

package io.bigsense.server

import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
import org.eclipse.jetty.server.handler.DefaultHandler
import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.webapp.WebAppContext
import io.bigsense.servlet.{DBUpdateListener, MasterServlet}


class JettyServer extends ServerTrait {

  val server = new Server()
  val connector = new ServerConnector(server)
  connector.setPort(httpPort)
  server.setConnectors(Array(connector))

  val context = new ServletContextHandler()
  context.setContextPath(BigSenseServer.webRoot)
  context.addServlet(new MasterServlet().getClass, "/*")
  context.addEventListener(new DBUpdateListener())

  val fileContext = new WebAppContext()
  fileContext.setContextPath(BigSenseServer.contentRoot)
  fileContext.setResourceBase(BigSenseServer.getClass.getResource("/io/bigsense/web").toExternalForm)

  val handlers = new HandlerCollection()
  handlers.setHandlers(Array( fileContext, context, new DefaultHandler()))
  server.setHandler(handlers)

  override def startServer() {
    server.start
    server.join
  }

  override def stopServer() {
    server.stop
  }
}

The Tomcat implementation is a little more complicated. There wasn’t a lot of documentation on using Tomcat embedded and configured in code. In the following example, I create an instance of the org.apache.catalina.startup.Tomcat class. When adding Servlets to Tomcat, we need to give it a working directory for some reason. I use the java.io.tmpdir system property to get a temporary folder in a platform independent way. (Note: this may result in empty ./tomcat.8080 directories when running in your local build environment). I couldn’t figure out how to add an event listener, but I also realized my listener didn’t even use the context that it’s handed, so I just called it manually without a context. Finally, I couldn’t find an equivalent of Jetty’s setResourceBase for serving static content, so I had to create my own StaticContentServlet which we’ll look at in a moment.

package io.bigsense.server

import org.apache.catalina.startup.Tomcat
import io.bigsense.servlet.{StaticContentServlet, DBUpdateListener, MasterServlet}
import java.io.File


/**
 * Created by sumit on 4/28/14.
 */
class TomcatServer extends ServerTrait {

  val tomcat = new Tomcat()
  tomcat.setPort(httpPort)
  val tmp = new File(System.getProperty("java.io.tmpdir"))

  val ctx = tomcat.addContext(BigSenseServer.webRoot,tmp.getAbsolutePath)
  Tomcat.addServlet(ctx,"bigsense",new MasterServlet())
  ctx.addServletMapping("/*","bigsense")

  new DBUpdateListener().contextInitialized(null)

  val cCtx = tomcat.addContext(BigSenseServer.contentRoot,tmp.getAbsolutePath)
  Tomcat.addServlet(cCtx,"static",new StaticContentServlet)
  cCtx.addServletMapping("/*","static")

  override def startServer() = {
    tomcat.start()
    tomcat.getServer().await()
  }

  override def stopServer() = tomcat.stop
}

The static content servlet is pretty basic. It simply finds a class path resource and returns it. The tricky part is setting the correct Mime-Type. I attempted to use the javax.activation.FileTypeMap to get the correct mime types based on extensions, but I often found it gave me the wrong results. As such, I hard coded the known mime types for the static files I knew were contained in my project.

package io.bigsense.servlet

import javax.servlet.http.{HttpServletRequest, HttpServletResponse, HttpServlet}
import javax.activation.FileTypeMap
import com.google.common.io.ByteStreams
import java.net.URLConnection

import org.slf4j.LoggerFactory

/**
 * needed to serve static resources for Tomcat. Not needed for Jetty
 *
 * Created by cassius on 29/04/14.
 */
class StaticContentServlet extends HttpServlet {

  val log = LoggerFactory.getLogger(this.getClass())

  override def doGet(req : HttpServletRequest, resp : HttpServletResponse) {

    val resourcePath = "/io/bigsense/web/%s".format(req.getPathInfo.stripPrefix("/"))

    val resource = getClass.getResource(resourcePath)

    log.debug("Requesting static resource %s".format(resourcePath))

    if(resource == null) {
      resp.setContentType("text/plain")
      resp.setStatus(HttpServletResponse.SC_NOT_FOUND)
      resp.getWriter.write("Not Found")
      resp.getWriter.close
    }
    else {
      resp.setContentType( getContentType(req.getPathInfo) )
      ByteStreams.copy(getClass.getResourceAsStream(resourcePath), resp.getOutputStream)
      resp.getOutputStream.close
    }
  }

  def getContentType(fileName : String) = {
    fileName match {
      case x if x endsWith "js" => "application/javascript"
      case x if x endsWith "css" => "text/css"
      case _ => Option[String](URLConnection.guessContentTypeFromName(fileName)) match {
        case Some(s) => s
        case None => FileTypeMap.getDefaultFileTypeMap().getContentType(fileName)
      }
    }

  }


}

The dependencies are fairly straightforward as well. I simply added the Tomcat and Jetty packages I needed. The following shows the dependencies in a buiuld.sbt, but you can easily do the same thing in Maven, Gradel or Ivy. Be sure to check to make sure you’re using the latest versions of Jetty and/or Tomcat as they might have changed since this example.

...
    "org.eclipse.jetty" % "jetty-server" % "9.1.4.v20140401",
    "org.eclipse.jetty" % "jetty-servlet" % "9.1.4.v20140401",
    "org.eclipse.jetty" % "jetty-webapp" % "9.1.4.v20140401",
    "org.rogach" %% "scallop" % "0.9.5",
    "org.apache.tomcat.embed" % "tomcat-embed-core"         % "7.0.53" ,
    "org.apache.tomcat.embed" % "tomcat-embed-logging-juli" % "7.0.53" ,
    "org.apache.tomcat.embed" % "tomcat-embed-jasper"       % "7.0.53" ,
    ...

From here, it’s very easy to create a main function and run your server. I use the sbt-native-packager plugin to create the appropriate deb and rpm files with the associated init scripts or SystemD service files. Doing so allows me to install BigSense as a standard Linux package and start it as a standard service with no dependencies on a system Tomcat package and no messy war or ear files.

There are disadvantages to this of course. If you run a lot of web applications and deploy them all this way, you’re going to start a whole Tomcat and JVM instance for each application. Even though Tomcat is relatively light (compared to say JBoss or WebSphere), it’s still heavy and can still take up quite a lot of resources, especially on a hosted virtual machines.

If you’re writing a lot of custom apps in house, it’s probably better to use the system’s Tomcat package and use a deployment system like Fabric to maintain, update and deploy your web applications. If you’re packaging your application for others, embedding Tomcat in this manner seems like a much better solution. However you need to be vigilant to update your packages when security vulnerability are discovered. Adding things like SSL can also be complicated with an embedded approach and it’s best to inform the user to use a front end like HAProxy or Nginx to handle SSL termination.

If you’re writing an application from scratch, you should consider avoiding the Servlet model entirely. On the JVM, there are projects such as Spray and Netty that provide asynchronous frameworks for web services and applications that leave behind the ancient HTTPServlet API that was originally designed in 19952!

I hope this tutorial has helped you if your goal was to learn how to embedded Tomcat into your web application for distribution. Please keep in mind that APIs change and some of these examples may need to be adjusted for your version of the given jars and platform. Although I only covered Tomcat and Jetty, there are other embeddable servlet engines that can be implemented in a similar manner, and even newer non-servlet engines like Spray and Netty do have servlet wrappers so you can use them with older web applications.

1 Confluence 5.6 Upgrade Notes: End of support announcements

2 Servlet History. 10 December 2005. Driscoll. Java.net.