Ahead of Time compilation support
Table of Contents
Introduction
Tomcat supports using the GraalVM/Mandrel Native Image tool to produce a native binary including the container. This documentation page describes the build process of such an image.
Setup
The native image tool is much easier to use with single JARs, as a result the process will use the Maven shade plugin JAR packaging. The idea is to produce a single JAR that contains all necessary classes from Tomcat, the webapps and all additional dependencies. Although Tomcat has received compatibility fixes to support native images, other libraries may not be compatible and may require replacement code (the GraalVM documentation has more details about this).
Download and install GraalVM or Mandrel.
Download the Tomcat Stuffed module from
https://github.com/apache/tomcat/tree/main/modules/stuffed
.
For convinience, an env property can be set:
export TOMCAT_STUFFED=/absolute...path...to/stuffed
Packaging and Building
Inside the $TOMCAT_STUFFED
folder, the directory structure
is the same as for regular Tomcat. The main configuration files
are placed in the conf
folder, and if using the default
server.xml
the webapps are placed in the webapps
folder.
All the webapp classes need to be made available to the Maven shade plugin
as well as the compiler during the JSP precompilation step.
Any JARs that are present in /WEB-INF/lib
need to be made available as Maven dependencies.
The webapp-jspc.ant.xml
script will copy classes from the
/WEB-INF/classes
folder of the webapp
to the target/classes
path that Maven uses as the compilation
target, but if any of the JSP sources use them, then they need to be
packaged as JARs instead.
The first step is to build the shaded Tomcat JAR with all dependencies.
Any JSP in the webapp must all be precompiled and packaged (assuming
that the webapps
contains a $WEBAPPNAME
webapp):
cd $TOMCAT_STUFFED
mvn package
ant -Dwebapp.name=$WEBAPPNAME -f webapp-jspc.ant.xml
$TOMCAT_STUFFED/pom.xml
,
following by building the shaded JAR:
mvn package
As it is best to avoid using reflection whenever possible with Ahead of Time compilation, it can be a good idea to generate and compile Tomcat Embedded code out of the main server.xml configuration as well as the context.xml files used to configure the contexts.
$JAVA_HOME/bin/java\
-Dcatalina.base=. -Djava.util.logging.config.file=conf/logging.properties\
-jar target/tomcat-stuffed-1.0.jar --catalina -generateCode src/main/java
mvn package
--catalina -useGeneratedCode
arguments are added to the
command lines. If this was not the case, they should be removed.
Native image configuration
Native images do not support any form of dynamic classloading or reflection unless it is defined explicitly in descriptors. Generating them uses a tracing agent from the GraalVM, and needs additional manual configuration in some cases.
Run Tomcat using the GraalVM substrate VM and its trace agent:
$JAVA_HOME/bin/java\
-agentlib:native-image-agent=config-output-dir=$TOMCAT_STUFFED/target/\
-Dorg.graalvm.nativeimage.imagecode=agent\
-Dcatalina.base=. -Djava.util.logging.config.file=conf/logging.properties\
-jar target/tomcat-stuffed-1.0.jar --catalina -useGeneratedCode
Now all paths from the webapp that lead to dynamic classloading (ex: Servlet access, websockets, etc) need to be accessed using a script that will exercise the webapp. Servlets may be loaded on startup instead of needing an actual access. Listeners may also be used to load additional classes on startup. When that is done, Tomcat can be stopped.
The descriptors have now been generated in the agent output directory. At this point, further configuration must be made to add items that are not traced, including: base interfaces, resource bundles, BeanInfo based reflection, etc. Please refer to the Graal documentation for more information on this process.
Even though all classes that are used have to be complied AOT into the
native image, webapps must still be left unchanged, and continue including
all needed classes and JARs in the WEB-INF
folder. Although
these classes will not actually be run or loaded, access to them is
required.
Building the native image
If everything has been done properly, the native image can now be built using the native-image tool.
$JAVA_HOME/bin/native-image --report-unsupported-elements-at-runtime\
--enable-http --enable-https --enable-url-protocols=http,https,jar,jrt\
--initialize-at-build-time=org.eclipse.jdt,org.apache.el.parser.SimpleNode,jakarta.servlet.jsp.JspFactory,org.apache.jasper.servlet.JasperInitializer,org.apache.jasper.runtime.JspFactoryImpl\
-H:+UnlockExperimentalVMOptions\
-H:+JNI -H:+ReportExceptionStackTraces\
-H:ConfigurationFileDirectories=$TOMCAT_STUFFED/target/\
-H:ReflectionConfigurationFiles=$TOMCAT_STUFFED/tomcat-reflection.json\
-H:ResourceConfigurationFiles=$TOMCAT_STUFFED/tomcat-resource.json\
-H:JNIConfigurationFiles=$TOMCAT_STUFFED/tomcat-jni.json\
-jar $TOMCAT_STUFFED/target/tomcat-stuffed-1.0.jar
--static
parameter enables static linking of
glibc, zlib and libstd++ in the generated binary.
Running the native image is then:
./tomcat-stuffed-1.0 -Dcatalina.base=. -Djava.util.logging.config.file=conf/logging.properties --catalina -useGeneratedCode
Compatibility
Servlets, JSPs, EL, websockets, the Tomcat container, tomcat-native, HTTP/2 are all supported out of the box in a native image.
At the time of writing this documentation, JULI is not supported as the log manager configuration property is not supported by Graal, in addition to some static initializer problems, and the regular java.util.logging loggers and implementation should be used instead.
If using the default server.xml file, some Server listeners have to be removed from the configuration as they are not compatible with native images, such as a JMX listener (JMX is unsupported) and leak prevention listeners (use of internal code that does not exist in Graal).
Missing items for better Tomcat functionality:
- java.util.logging LogManager: Configuration through a system property is not implemented, so standard java.util.logging must be used instead of JULI
- Static linking configuration: tomcat-native cannot be statically linked