www.rodneybeede.com "I would love to change the world, but they won't give me the source code" - unknown
 Navigation

Embedding Tomcat 7 in a war file - Last Modified 2011-08-25 16:28 UTC - Created 2011-07-01 19:21 UTC

All code is Copyright 2011 Rodney Beede
Permission to use is licensed under the GNU AFFERO GENERAL PUBLIC LICENSE, Version 3, http://www.gnu.org/licenses/agpl.txt

So Jetty has the ability to java -jar YourWebApp.war which runs the Jetty server. I tried using Jetty 8 to do this with an application that had JSP files, but support just isn't there yet. I wanted the latest JSP APIs too.

I decided to try Tomcat 7's new embedded support. I used some known tricks from my Jetty experience to get it working correctly. This allows me to embed Tomcat 7 into my war file for easy deployment to others with java -jar WebApp.war. I used Maven for all the building which makes grabbing dependency jars much easier. The embedded version reads the web application configuration from the normal WEB-INF/web.xml. Here is a rundown:

pom.xml

Standard stuff for any web application project. Your build type should be a war not a jar.

		<!-- These allow embedding of Tomcat 7 -->
		<dependency>
			<groupId>org.apache.tomcat</groupId>
			<artifactId>tomcat-catalina</artifactId>
			<version>7.0.16</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-core</artifactId>
			<version>7.0.16</version>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat</groupId>
			<artifactId>tomcat-jasper</artifactId>
			<version>7.0.16</version>
		</dependency>


	<build>
		<plugins>
			<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>
							<createDependencyReducedPom>true</createDependencyReducedPom>
							<transformers>
								<transformer
									implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
									<mainClass>EmbeddedTomcatMain</mainClass>
								</transformer>
							</transformers>
						</configuration>
					</execution>
				</executions>
			</plugin>
			<!-- The main class needs to be in the root of the war in order to be runnable -->
			<plugin>
				<artifactId>maven-antrun-plugin</artifactId>
				<executions>
					<execution>
						<id>move-main-class</id>
						<phase>compile</phase>
						<configuration>
							<tasks>
								<move todir="${project.build.directory}/${project.artifactId}">
									<fileset dir="${project.build.directory}/classes/">
										<include name="EmbeddedTomcatMain.class" />
									</fileset>
								</move>
							</tasks>
						</configuration>
						<goals>
							<goal>run</goal>
						</goals>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>


	<repositories>
		<!-- Because Maven Central repository is slow at getting the latest JSP 
			servlet, jstl, and other apis -->
		<repository>
			<id>javaNetGlassfish</id>
			<name>java.net Glassfish Maven Repository</name>
			<url>http://download.java.net/maven/glassfish/</url>
			<layout>default</layout>
		</repository>
		<repository>
			<id>javaNet2</id>
			<name>java.net Maven 2 Repository</name>
			<url>http://download.java.net/maven/2/</url>
			<layout>default</layout>
		</repository>
		<repository>
			<id>ObjectWeb</id>
			<name>ow2.org</name>
			<url>http://maven.ow2.org/maven2/</url>
			<layout>default</layout>
		</repository>
	</repositories>

EmbeddedTomcatMain.java


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.ProtectionDomain;

import javax.servlet.ServletException;

import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.tomcat.util.http.fileupload.IOUtils;

/**
 * @author rbeede
 * 
 * Supports Tomcat 7
 * 
 */
public class EmbeddedTomcatMain {
	public static void main(final String[] args) throws ServletException, LifecycleException, URISyntaxException, IOException {
		final Tomcat tomcat = new Tomcat();
		
		
		// Extract the pre-packaged SSL keys
		final File[] certificateStores = extractCertificateStores();
		
		
		
		tomcat.setPort(80);  // Default connector
		
		addConnector(82, false, tomcat, null);
		addConnector(443, true, tomcat, certificateStores);
		
        
		// Load the war (assumes this class in in root of war file)
		final ProtectionDomain domain = EmbeddedTomcatMain.class.getProtectionDomain();
		final URL location = domain.getCodeSource().getLocation();

		System.out.println("Using webapp at " + location.toExternalForm());


		tomcat.addWebapp("/", location.toURI().getPath());
		tomcat.start();
		tomcat.getServer().await();
	}
	
	
	private static void addConnector(final int port, final boolean https, final Tomcat tomcat, final File[] certificateStores) throws IOException {
		final Connector connector = new Connector();
		connector.setScheme((https) ? "https" : "http");
		connector.setPort(port);
		connector.setProperty("maxPostSize", "0");  // unlimited
		connector.setProperty("xpoweredBy", "true");
		if(https) {
			connector.setSecure(true);
			connector.setProperty("SSLEnabled","true");
			connector.setProperty("keyPass", "123456");
			connector.setProperty("keystoreFile", certificateStores[0].getCanonicalPath());
			connector.setProperty("keystorePass", "123456");
			connector.setProperty("truststoreFile", certificateStores[1].getCanonicalPath());
			connector.setProperty("truststorePass", "123456");
		}
		tomcat.getService().addConnector(connector);
	}
	
	
	/**
	 * @param tomcat
	 * @return 0 = keystoreFile, 1 = truststoreFile
	 * @throws IOException 
	 */
	private static File[] extractCertificateStores() throws IOException {
		//FIXME Not secure in creation because Java 6 and before provide no platform independent API for setting file permissions & ownership.  Java 7 will.
		final File keystoreFile = File.createTempFile("ETM", null);
		final File truststoreFile = File.createTempFile("ETM", null);
		
		keystoreFile.deleteOnExit();
		truststoreFile.deleteOnExit();
		
		final FileOutputStream fosKeystore = new FileOutputStream(keystoreFile);
		final FileOutputStream fosTruststore = new FileOutputStream(truststoreFile);
		
		// Assumes .jks files were in root of project resources which causes them to be under /WEB-INF/classes/ inside the war
		IOUtils.copy(EmbeddedTomcatMain.class.getResourceAsStream("/WEB-INF/classes/keyStore.jks"), fosKeystore);
		IOUtils.copy(EmbeddedTomcatMain.class.getResourceAsStream("/WEB-INF/classes/trustStore.jks"), fosTruststore);
		
		fosKeystore.close();
		fosTruststore.close();
		
		
		return new File[] {keystoreFile, truststoreFile};
	}
}

Eclipse Notes

If you use the WTP and Eclipse's Servers to run your web application inside Eclipse you will get errors from your Tomcat at startup along the lines of "java.lang.NoSuchMethodException: org.apache.catalina.deploy.WebXml addServlet"

This happens because your pom includes the Tomcat jars so they are being seen twice which causes your Eclipse Tomcat to not load correctly. The Maven Eclipse plug-in doesn't have a way to customize the Deployment Assembly on your project to exclude these for the Eclipse project. You have to go into your project properties, Deployment Assembly, and remove all the "org/apache/tomcat/..." entries. Everytime you mvn eclipse:eclipse you'll have to do this again. You could do a work around and write a custom Maven command in your pom that modifies the ".settings/org.eclipse.wst.common.component" file I suppose.

Another option would be to make a separate project and pom just for Tomcat embedding. You could then attach the projects at the correct build stage in Maven so your Eclipse project would be cleaner.