Micro-Service with embedded Tomcat

Micro-services are one of the new big cool things in Java Enterprise environments. Since there are a lot of operators out there who spent half of their carrers with learning how to get along with huge Websphere-, JBoss- or Weblogic-application-server-installations, companies decide to use and re-use their already paid infrastructure-licenses and the existing know-how. But for many young unfledged projects within startups or even modern-thinking (geeky?) operators and devs in larger companies it is clear that a high-cost-infrastructure needs more resources (personal, financial and operational) then having a simple Jar-file with a shell-script beside to run. This is called Micro-Service where „Micro“ is no synonym for the size of the service at all. It is more an approach for the minimalistic form of the deployment or build-artifact. In connection to continous-delivery-processes it is even more easy to build and deliver a new Jar, then the complex artifact of an EAR with app-server-configuration, huge instruction-manuals etc.

So in this example I wrote my own Micro-Service based on an embedded Tomcat via Maven. Additionally I let Maven create and pack the corresponding shell-script within the build. After normal compile, the maven-shade-plugin will put all dependency-classes into the resulting-artifact. (So better keep an eye on clean package-structures.) In the end I will use copy-maven-plugin by evgeny-goldin to copy and filter the run-script.

Environment:
MVN: 3.0.5 (because the copy-maven-plugin cannot handle newer maven-versions :-/)
JDK: 1.8.0_05

The project-setup:
embedded-tomcat

package org.jtaddeus.embeddedtomcat;

import java.io.File;

import org.apache.catalina.Context;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
import org.jtaddeus.embeddedtomcat.servlets.LoginHttpServlet;
import org.jtaddeus.embeddedtomcat.servlets.DashboardHttpServlet;

public class Start {
	
	public static final int PORT = 11111;

    public static void main(String[] args) throws LifecycleException {
        Tomcat tomcat = new Tomcat();
        tomcat.setPort(PORT);

        Context ctx = tomcat.addContext("/", new File(".").getAbsolutePath());

        Tomcat.addServlet(ctx, "dashboard", new DashboardHttpServlet());
        Tomcat.addServlet(ctx, "login", new LoginHttpServlet());         
        
        // order is important!
        ctx.addServletMapping("/login", "login"); 
        ctx.addServletMapping("/*", "dashboard");     

        tomcat.start();
        tomcat.getServer().await();
    }
}
package org.jtaddeus.embeddedtomcat.servlets;

import java.io.IOException;
import java.io.Writer;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * This class represents the servlet for Dashboard-actions.
 */
public class DashboardHttpServlet extends HttpServlet {

	/** The serial. */
	private static final long serialVersionUID = -3952071805773483801L;

	protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        String loginPagePath = req.getContextPath() + "/login";

        Writer w = resp.getWriter();
        w.write("<h1>Dashboard</h1>");
        w.write("<a href=\"" + loginPagePath + "\">Login</a>");
        w.flush();
    }
}
<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/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.jtaddeus</groupId>
	<artifactId>embedded-tomcat</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>
		<tomcat.version>8.0.5</tomcat.version>
		<servlet.api.version>3.1.0</servlet.api.version>
	</properties>

	<build>
		<resources>
			<resource>
				<directory>src/main/scripts</directory>
			</resource>
		</resources>
		<plugins>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-shade-plugin</artifactId>
				<version>2.3</version>
				<executions>
					<execution>
						<phase>package</phase>
						<goals>
							<goal>shade</goal>
						</goals>
						<configuration>
							<transformers>
								<transformer
									implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
									<mainClass>org.jtaddeus.embeddedtomcat.Start</mainClass>
								</transformer>
							</transformers>
						</configuration>
					</execution>
				</executions>
			</plugin>
			<plugin>
				<groupId>com.github.goldin</groupId>
				<artifactId>copy-maven-plugin</artifactId>
				<version>0.2.5</version>
				<executions>
					<execution>
						<id>copy-run-scripts</id>
						<phase>package</phase>
						<goals>
							<goal>copy</goal>
						</goals>
						<configuration>
							<resources>
								<resource>
									<targetPath>${project.basedir}/target</targetPath>
									<directory>${project.basedir}/src/scripts</directory>
									<include>*.cmd</include>
									<filtering>true</filtering>
									<encoding>UTF8</encoding>
								</resource>
							</resources>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

	<dependencies>
		<!-- servlet -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>javax.servlet-api</artifactId>
			<version>${servlet.api.version}</version>
		</dependency>
		<!-- tomcat -->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-core</artifactId>
			<version>${tomcat.version}</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-logging-log4j</artifactId>
			<version>${tomcat.version}</version>
		</dependency>
	</dependencies>
</project>	
rem run-script for the embedded-tomcat-example-app
java -jar ./${project.build.finalName}.jar

So basically, that’s it. The devs can dive into the sources and change what ever they want and all, really all, the operator has to do is getting some cola or coffee and replace the shell-script and the Jar for a new deployment. While pushing this all to a continous-deployment-pipeline, the operators can join the dev-teams for adding their knowledge to the sources.

Please note: if you run the run.cmd on your win-machine with a 1.6-JRE, you’ll facing following error:

Exception in thread "main" java.lang.UnsupportedClassVersionError: javax/servlet/Servlet : Unsupported major.minor version 51.0
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
        at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
Could not find the main class: org.jtaddeus.embeddedtomcat.Start. Program will exit.

That means, you have to use a newer version of JRE, because Tomcat8 needs at least a JRE7!