Migrate Tomcat applications to Tomcat on Azure App Service

This guide describes what you should be aware of when you want to migrate an existing Tomcat application to run on Azure App Service using Tomcat 9.0.

Pre-migration

To ensure a successful migration, before you start, complete the assessment and inventory steps described in the following sections.

If you can't meet any of these pre-migration requirements, see the following companion migration guides:

Switch to a supported platform

App Service offers specific versions of Tomcat on specific versions of Java. To ensure compatibility, migrate your application to one of the supported versions of Tomcat and Java in its current environment before you continue with any of the remaining steps. Be sure to fully test the resulting configuration. Use the latest stable release of your Linux distribution in such tests.

Note

This validation is especially important if your current server is running on an unsupported JDK (such as Oracle JDK or IBM OpenJ9).

To obtain your current Java version, sign in to your production server and run the following command:

java -version

On Azure App Service, the binaries for Java 8 are provided from Eclipse Temurin. For Java 11, 17, and all future LTS releases of Java, App Service provides the Microsoft Build of OpenJDK. These binaries are available for free download at the following sites:

To obtain your current Tomcat version, sign in to your production server and run the following command:

${CATALINA_HOME}/bin/version.sh

To obtain the current version used by Azure App Service, download Tomcat 9, depending on which version you plan to use in Azure App Service.

Inventory external resources

External resources, such as data sources, JMS message brokers, and others are injected via Java Naming and Directory Interface (JNDI). Some such resources may require migration or reconfiguration.

Inside your application

Inspect the META-INF/context.xml file. Look for <Resource> elements inside the <Context> element.

On the application server(s)

Inspect the $CATALINA_BASE/conf/context.xml and $CATALINA_BASE/conf/server.xml files as well as the .xml files found in $CATALINA_BASE/conf/[engine-name]/[host-name] directories.

In context.xml files, JNDI resources will be described by the <Resource> elements inside the top-level <Context> element.

In server.xml files, JNDI resources will be described by the <Resource> elements inside the <GlobalNamingResources> element.

Datasources

Datasources are JNDI resources with the type attribute set to javax.sql.DataSource. For each datasource, document the following information:

  • What is the datasource name?
  • What is the connection pool configuration?
  • Where can I find the JDBC driver JAR file?

For more information, see JNDI Datasource HOW-TO in the Tomcat documentation.

All other external resources

It isn't feasible to document every possible external dependency in this guide. It's your team's responsibility to verify that you can satisfy every external dependency of your application after the migration.

Inventory secrets

Passwords and secure strings

Check all properties and configuration files on the production server(s) for any secret strings and passwords. Be sure to check server.xml and context.xml in $CATALINA_BASE/conf. You may also find configuration files containing passwords or credentials inside your application. These may include META-INF/context.xml, and, for Spring Boot applications, application.properties or application.yml files.

Inventory certificates

Document all the certificates used for public SSL endpoints or communication with backend databases and other systems. You can view all certificates on the production server(s) by running the following command:

keytool -list -v -keystore <path to keystore>

Determine whether and how the file system is used

Any usage of the file system on the application server will require reconfiguration or, in rare cases, architectural changes. You may identify some or all of the following scenarios.

Read-only static content

If your application currently serves static content, you'll need an alternate location for it. You may wish to consider moving static content to Azure Blob Storage and adding Azure CDN for lightning-fast downloads globally. For more information, see Static website hosting in Azure Storage and Quickstart: Integrate an Azure storage account with Azure CDN. You can also directly deploy the static content to an app in the Azure Spring Apps Enterprise plan. For more information, see Deploy web static files.

Dynamically published static content

If your application allows for static content that is uploaded/produced by your application but is immutable after its creation, you can use Azure Blob Storage and Azure CDN as described above, with an Azure Function to handle uploads and CDN refresh. We've provided a sample implementation for your use at Uploading and CDN-preloading static content with Azure Functions. You can also directly deploy the static content to an app in the Azure Spring Apps Enterprise plan. For more information, see Deploy web static files.

Dynamic or internal content

For files that are frequently written and read by your application (such as temporary data files), or static files that are visible only to your application, you can mount Azure Storage into the App Service file system. For more information, see Serve content from Azure Storage in App Service on Linux.

Identify session persistence mechanism

To identify the session persistence manager in use, inspect the context.xml files in your application and Tomcat configuration. Look for the <Manager> element, and then note the value of the className attribute.

Tomcat's built-in PersistentManager implementations, such as StandardManager or FileStore aren't designed for use with a distributed, scaled platform such as App Service. Because App Service may load balance among several instances and transparently restart any instance at any time, persisting mutable state to a file system isn't recommended.

If session persistence is required, you'll need to use an alternate PersistentManager implementation that will write to an external data store, such as VMware Tanzu Session Manager with Redis Cache. For more information, see Use Redis as a session cache with Tomcat.

Identify all outside processes and daemons running on the production servers

If you have any processes running outside the application server, such as monitoring daemons, you'll need to eliminate them or migrate them elsewhere.

Special cases

Certain production scenarios may require additional changes or impose additional limitations. While such scenarios can be infrequent, it's important to ensure that they're either inapplicable to your application or correctly resolved.

Determine whether application relies on scheduled jobs

Scheduled jobs, such as Quartz Scheduler tasks or cron jobs, can't be used with App Service. App Service won't prevent you from deploying an application containing scheduled tasks internally. However, if your application is scaled out, the same scheduled job may run more than once per scheduled period. This situation can lead to unintended consequences.

Inventory any scheduled jobs, inside or outside the application server.

Determine whether your application contains OS-specific code

If your application contains any code with dependencies on the host OS, then you'll need to refactor it to remove those dependencies. For example, you may need to replace any use of / or \ in file system paths with File.Separator or Paths.get.

Determine whether Tomcat clustering is used

Tomcat clustering isn't supported on Azure App Service. Instead, you can configure and manage scaling and load balancing through Azure App Service without Tomcat-specific functionality. You can persist session state to an alternate location to make it available across replicas. For more information, see Identify session persistence mechanism.

To determine whether your application uses clustering, look for the <Cluster> element inside the <Host> or <Engine> elements in the server.xml file.

Determine whether non-HTTP connectors are used

App Service supports only a single HTTP connector. If your application requires additional connectors, such as the AJP connector, don't use App Service.

To identify HTTP connectors used by your application, look for <Connector> elements inside the server.xml file in your Tomcat configuration.

Determine whether MemoryRealm is used

MemoryRealm requires a persisted XML file. On Azure AppService, you'll need to upload this file to the /home directory or one of its subdirectories, or to mounted storage. You'll then need to modify the pathName parameter accordingly.

To determine whether MemoryRealm is currently used, inspect your server.xml and context.xml files and search for <Realm> elements where the className attribute is set to org.apache.catalina.realm.MemoryRealm.

Determine whether SSL session tracking is used

App Service performs session offloading outside of the Tomcat runtime, so you can't use SSL session tracking. Use a different session tracking mode instead (COOKIE or URL). If you need SSL session tracking, don't use App Service.

Determine whether AccessLogValve is used

If you use AccessLogValve, you should set the directory parameter to /home/LogFiles or one of its subdirectories.

Migration

Parameterize the configuration

In the pre-migration steps, you likely identified some secrets and external dependencies, such as datasources, in server.xml and context.xml files. For each item you identified, replace any username, password, connection string, or URL with an environment variable.

For example, suppose the context.xml file contains the following element:

<Resource
    name="jdbc/dbconnection"
    type="javax.sql.DataSource"
    url="jdbc:postgresql://postgresdb.contoso.com/wickedsecret?ssl=true"
    driverClassName="org.postgresql.Driver"
    username="postgres"
    password="t00secure2gue$$"
/>

In this case, you could change it as shown in the following example:

<Resource
    name="jdbc/dbconnection"
    type="javax.sql.DataSource"
    url="${postgresdb.connectionString}"
    driverClassName="org.postgresql.Driver"
    username="${postgresdb.username}"
    password="${postgresdb.password}"
/>

To ensure that parameter substitution occurs for any context.xml file within the META-INF folder inside a deployed .war file, be sure to set the CATALINA_OPTS environment variable as shown in the following example:

export CATALINA_OPTS="-Dorg.apache.tomcat.util.digester.PROPERTY_SOURCE=org.apache.tomcat.util.digester.EnvironmentPropertySource"

Provision an App Service plan

From the list of available service plans at App Service pricing, select the plan whose specifications meet or exceed those of the current production hardware.

Note

If you plan to run staging/canary deployments or use deployment slots, the App Service plan must include that additional capacity. We recommend using Premium or higher plans for Java applications. For more information, see Set up staging environments in Azure App Service.

Then, create the App Service plan. For more information, see Manage an App Service plan in Azure.

Create and deploy Web App(s)

You'll need to create a Web App on your App Service Plan (choosing a version of Tomcat as the runtime stack) for every WAR file deployed to your Tomcat server.

Note

While it's possible to deploy multiple WAR files to a single web app, this is highly undesirable. Deploying multiple WAR files to a single web app prevents each application from scaling according to its own usage demands. It also adds complexity to subsequent deployment pipelines. If multiple applications need to be available on a single URL, consider using a routing solution such as Azure Application Gateway.

Maven applications

If your application is built from a Maven POM file, use the Webapp plugin for Maven to create the Web App and deploy your application.

Non-Maven applications

If you can't use the Maven plugin, you'll need to provision the Web App through other mechanisms, such as:

Once the Web App has been created, use one of the available deployment mechanisms to deploy your application.

Migrate JVM runtime options

If your application requires specific runtime options, use the most appropriate mechanism to specify them.

Populate secrets

Use Application Settings to store any secrets specific to your application. If you intend to use the same secret(s) among multiple applications or require fine-grained access policies and audit capabilities, use Azure Key Vault instead.

Configure custom domain and SSL

If your application will be visible on a custom domain, you'll need to map your web application to it. For more information, see Tutorial: Map an existing custom DNS name to Azure App Service.

Then, you'll need to bind the SSL certificate for that domain to your App Service Web App. For more information, see Secure a custom DNS name with an SSL binding in Azure App Service.

Import backend certificates

All certificates for communicating with backend systems, such as databases, need to be made available to App Service. For more information, see Add an SSL certificate in App Service.

Migrate data sources, libraries, and JNDI resources

For data source configuration steps, see the Data sources section of Configure a Linux Java app for Azure App Service.

For additional data source instructions, see the following sections of the JNDI Datasource How-To in the Tomcat documentation:

Migrate any additional server-level classpath dependencies by following the same steps as for data source JAR files.

Migrate any additional Shared server-level JDNI resources.

Note

If you're following the recommended architecture of one WAR per webapp, consider migrating server-level classpath libraries and JNDI resources into your application. This will significantly simplify component governance and change management.

Migrate remaining configuration

Upon completing the preceding section, you should have your customizable server configuration in /home/tomcat/conf.

Complete the migration by copying any additional configuration (such as realms and JASPIC)

Migrate scheduled jobs

To execute scheduled jobs on Azure, consider using a Timer trigger for Azure Functions. You don't need to migrate the job code itself into a function. The function can simply invoke a URL in your application to trigger the job. If such job executions have to be dynamically invoked and/or centrally tracked, consider using Spring Batch.

Alternatively, you can create a Logic app with a Recurrence trigger to invoke the URL without writing any code outside your application. For more information, see Overview - What is Azure Logic Apps? and Create, schedule, and run recurring tasks and workflows with the Recurrence trigger in Azure Logic Apps.

Note

To prevent malicious use, you'll likely need to ensure that the job invocation endpoint requires credentials. In this case, the trigger function will need to provide the credentials.

Restart and smoke-test

Finally, you'll need to restart your Web App to apply all configuration changes. Upon completion of the restart, verify that your application is running correctly.

Post-migration

Now that you have your application migrated to Azure App Service you should verify that it works as you expect. Once you've done that we have some recommendations for you that can make your application more Cloud native.

Recommendations