Migrating your App Engine app from Java 8 to Java 11

In addition to supporting the Java 8 runtime, App Engine now supports hosting your applications using the Java 11 runtime. Learn how to migrate your existing App Engine app from Java 8 to Java 11 on the standard environment.

Creating an app.yaml file

In the Java 11 runtime, application settings are stored in the app.yaml file. You must remove the appengine-web.xml file from your existing application and create an app.yaml file.

The following is a sample Maven project structure:

MyDir/
  pom.xml
  [index.yaml]
  [cron.yaml]
  [dispatch.yaml]
  src/main/
    appengine/
      app.yaml
    java/com.example.mycode/
      MyCode.java

Your app.yaml file requires only the runtime element for a simple Java app:

runtime: java11
# No need for an entrypoint with single fatjar with correct manifest class-path entry.

By specifying runtime: java11 the Java 11 runtime is automatically selected when you deploy a JAR file.

If you have more than one JAR file in your project directory or want to specify a custom entrypoint, you must specify it in the entrypoint element of your app.yaml file. For more information, see app.yaml file reference.

Re-packaging a WAR file into a JAR file

To migrate to the Java 11 runtime, you must re-package your App Engine Java 8 web application into an executable JAR file.

Your application must have a Main class that starts a web server which responds to HTTP requests on the port specified by the PORT environment variable, typically 8080.

For example:

import com.sun.net.httpserver.HttpServer;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

public class Main {

  public static void main(String[] args) throws IOException {
    // Create an instance of HttpServer bound to port defined by the
    // PORT environment variable when present, otherwise on 8080.
    int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "8080"));
    HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);

    // Set root URI path.
    server.createContext("/", (var t) -> {
      byte[] response = "Hello World from Google App Engine Java 11.".getBytes();
      t.sendResponseHeaders(200, response.length);
      try (OutputStream os = t.getResponseBody()) {
        os.write(response);
      }
    });

    // Start the server.
    server.start();
  }
}

WAR migration example

The following instructions demonstrate how to repackage an App Engine Java 8 hello-world application as a JAR to run on the Java 11 runtime.

The migration uses the appengine-simple-jetty-main artifact. This provides a Main class with a simple Jetty web server that loads a WAR file and packages your app into an executable JAR file:

  1. Clone the Embedded Jetty Server artifact to your local machine:

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples
    

    Alternatively, you can download the sample as a zip file and extract it.

  2. Change to the directory that contains the sample code:

    cd java-docs-samples/appengine-java11/appengine-simple-jetty-main/
    
  3. Install the dependency locally:

    mvn install
    
  4. Add the following code to your project pom.xml file:

    • appengine-simple-jetty-main dependency:
      <dependency>
        <groupId>com.example.appengine.demo</groupId>
        <artifactId>simple-jetty-main</artifactId>
        <version>1</version>
        <scope>provided</scope>
      </dependency>
    • maven-dependency plugin:
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>3.1.1</version>
        <executions>
          <execution>
            <id>copy</id>
            <phase>prepare-package</phase>
            <goals>
              <goal>copy-dependencies</goal>
            </goals>
            <configuration>
              <outputDirectory>
                ${project.build.directory}/appengine-staging
              </outputDirectory>
            </configuration>
          </execution>
        </executions>
      </plugin>
      App Engine deploys files located in the ${build.directory}/appengine-staging directory. By adding the maven-dependency plugin to your build, App Engine installs your specified dependencies to the correct folder.
  5. Create an entrypoint element in your app.yaml file to call the appengine-simple-jetty-main object and pass your WAR file as an argument. For example, see the helloworld-servlet sample app.yaml file:

    runtime: java11
    instance_class: F1
    entrypoint: 'java -cp "*" com.example.appengine.demo.jettymain.Main helloworld.war'
  6. To run your application locally:

    1. Package your application:

      mvn clean package
      
    2. Start the server with your WAR file as an argument.

      For example, you can start the server in the helloworld-servlet sample by running the following command from your java-docs-samples/appengine-java11/appengine-simple-jetty-main/ folder:

      mvn exec:java -Dexec.args="../helloworld-java8/target/helloworld.war"
      
    3. In your web browser, enter the following address:

      http://localhost:8080

  7. To deploy your application:

    gcloud tooling

    gcloud app deploy

    Maven plugin

    mvn appengine:deploy -Dapp.deploy.projectId=YOUR_PROJECT_ID

Framework flexibility

The Java 11 runtime does not include any web-serving framework, meaning you're no longer limited to servlet-based frameworks.

There are hello world samples using popular Java web frameworks on the Google Cloud Platform GitHub repository:

Migrating from the App Engine Java SDK

The Java 11 runtime does not support the App Engine Java SDK. The following lists some changes you may have to make to your existing App Engine Java 8 app and your deployment process in order to use the App Engine Java 11 runtime:

Migrating from the App Engine-specific APIs

Most of the functionality provided by the App Engine-specific APIs is now provided by the Google Cloud Client Library for Java. For more information, see the recommended alternatives listed below.

Blobstore

To store and retrieve data, use Cloud Storage through the Cloud Client Libraries. To get started, see Using Cloud Storage.

Datastore

For an alternative to a NoSQL key/value database like the Datastore API, use Cloud Firestore in Datastore mode. Cloud Firestore is the newest version of Cloud Datastore, and Datastore mode is recommended for databases that will be used primarily by App Engine apps.

Images

For manipulating and processing images, imgix is recommended. Otherwise, use Rethumb if you prefer having a free tier.

To store and serve your images, see Using Cloud Storage or Serving Static Files.

Logging

Request logs still appear in Stackdriver Logging, but they are no longer automatically correlated. To implement your desired logging behavior, you can use the Stackdriver Logging client libraries.

Mail

To send email, use a third-party mail provider such as SendGrid, Mailgun, or Mailjet. All of these services offer APIs to send emails from applications.

Memcache

To build an application cache, create a Cloud Memorystore instance and connect it to your app using Serverless VPC Access.

Modules

To obtain information and modify your application's running services, use a combination of environment variables and the App Engine Admin API:

Service information How to access
Current application ID GAE_APPLICATION environment variable
Current project ID GOOGLE_CLOUD_PROJECT environment variable
Current service name GAE_SERVICE environment variable
Current service version GAE_VERSION environment variable
Current instance ID GAE_INSTANCE environment variable
Default hostname Admin API apps.get method
List of services Admin API apps.services.list method
List of versions for a service Admin API apps.services.versions.list method
Default version for a service, including any traffic splits Admin API apps.services.get method
List of running instances for a version Admin API apps.services.versions.instances.list method

For more information about the data available about your application's running services, see Java 11 Runtime Environment.

Host any full-text search database such as Elasticsearch on Compute Engine and access it from your service.

Task queue

Queue tasks for asynchronous code execution using the Cloud Tasks REST API, RPC API, or the Google Cloud Client library, and use a Java 11 App Engine standard service as a Push target. For more information, see Migrating from Task Queues to Cloud Tasks.

In many cases where you might use pull queues, such as queuing up tasks or messages that will be pulled and processed by separate workers, Cloud Pub/Sub can be a good alternative as it offers similar functionality and delivery guarantees. See the following sample program interacting with the Cloud Tasks API.

User authentication

For an alternative to the Users API, use any HTTP-based authentication mechanism such as:

Migrating XML to YAML file formats

Cloud SDK does not support the following file formats:

  • cron.xml
  • datastore-index.xml
  • dispatch.xml
  • queue.xml

The following examples demonstrate how to migrate your xml files to yaml files.

Migrating your files automatically

To migrate your xml files automatically:

  1. You must have Cloud SDK version 226.0.0 or later. To update to the latest version:

    gcloud components update
    
  2. For each file you'd like to migrate, specify one of the following subcommands (cron-xml-to-yaml, datastore-indexes-xml-to-yaml, dispatch-xml-to-yaml, queue-xml-to-yaml) and file name:

    gcloud beta app migrate-config queue-xml-to-yaml MY-QUEUE-XML-FILE.xml
    
  3. Manually double-check the converted file before deploying to production.

    For a successful sample xml to yaml file conversion, see the Migrating your files manually tabs.

Migrating your files manually

To manually migrate your xml files to yaml files:

cron.yaml

Create a cron.yaml file with a cron object containing a list of objects, each with fields that correspond to each of the <cron> tag attributes in your cron.xml file, as shown below.

Converted cron.yaml file:

cron:
- url: '/recache'
  schedule: 'every 2 minutes'
  description: 'Repopulate the cache every 2 minutes'
- url: '/weeklyreport'
  schedule: 'every monday 08:30'
  target: 'version-2'
  timezone: 'America/New_York'
  description: 'Mail out a weekly report'

Original cron.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<cronentries>
  <cron>
    <url>/recache</url>
    <description>Repopulate the cache every 2 minutes</description>
    <schedule>every 2 minutes</schedule>
  </cron>
  <cron>
    <url>/weeklyreport</url>
    <description>Mail out a weekly report</description>
    <schedule>every monday 08:30</schedule>
    <timezone>America/New_York</timezone>
    <target>version-2</target>
  </cron>
</cronentries>

For more information, see the cron.yaml reference documentation.

dispatch.yaml

Create a dispatch.yaml file with a dispatch object containing a list of objects, each with fields that correspond to each of the <dispatch> tag attributes in your dispatch.xml file, as shown below.

Converted dispatch.yaml file:

dispatch:
- url: '*/favicon.ico'
  module: default
- url: 'simple-sample.appspot.com/'
  module: default
- url: '*/mobile/*'
  module: mobile-frontend

Original dispatch.xml file

<?xml version="1.0" encoding="UTF-8"?>
<dispatch-entries>
  <dispatch>
      <url>*/favicon.ico</url>
      <module>default</module>
  </dispatch>
  <dispatch>
      <url>simple-sample.appspot.com/</url>
      <module>default</module>
  </dispatch>
  <dispatch>
      <url>*/mobile/*</url>
      <module>mobile-frontend</module>
  </dispatch>
</dispatch-entries>

For more information, see the dispatch.yaml reference documentation

index.yaml

Create an index.yaml file with an indexes object containing a list of objects, each with fields that correspond to each of the <datastore-index> tag attributes in your datastore-indexes.xml file, as shown below.

Converted index.yaml file:

indexes:
- ancestor: false
  kind: Employee
  properties:
  - direction: asc
    name: lastName
  - direction: desc
    name: hireDate
- ancestor: false
  kind: Project
  properties:
  - direction: asc
    name: dueDate
  - direction: desc
    name: cost

Original datastore-index.xml file:

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes
 autoGenerate="true">
   <datastore-index kind="Employee" ancestor="false">
       <property name="lastName" direction="asc" />
       <property name="hireDate" direction="desc" />
   </datastore-index>
   <datastore-index kind="Project" ancestor="false">
       <property name="dueDate" direction="asc" />
       <property name="cost" direction="desc" />
   </datastore-index>
</datastore-indexes>

For more information, see the index.yaml reference documentation.

queue.yaml

Create a queue.yaml file with a queue object containing a list of objects, each with fields that correspond to each of the <queue> tag attributes in your queue.xml file, as shown below.

Converted queue.yaml file:

queue:
- name: fooqueue
  mode: push
  rate: 1/s
  retry_parameters:
    task_retry_limit: 7
    task_age_limit: 2d
- name: barqueue
  mode: push
  rate: 1/s
  retry_parameters:
    min_backoff_seconds: 10
    max_backoff_seconds: 200
    max_doublings: 0

Original queue.xml file:

<queue-entries>
  <queue>
    <name>fooqueue</name>
    <rate>1/s</rate>
    <retry-parameters>
      <task-retry-limit>7</task-retry-limit>
      <task-age-limit>2d</task-age-limit>
    </retry-parameters>
  </queue>
  <queue>
    <name>barqueue</name>
    <rate>1/s</rate>
    <retry-parameters>
      <min-backoff-seconds>10</min-backoff-seconds>
      <max-backoff-seconds>200</max-backoff-seconds>
      <max-doublings>0</max-doublings>
    </retry-parameters>
  </queue>
<queue-entries>
Was this page helpful? Let us know how we did:

Send feedback about...

App Engine standard environment for Java 11 docs