Notice: Over the next few months, we're reorganizing the App Engine documentation site to make it easier to find content and better align with the rest of Google Cloud products. The same content will be available, but the navigation will now match the rest of the Cloud products. If you have feedback or questions as you navigate the site, click Send Feedback.

Differences between Java 8 and Java 11/17

Migrating to the Java 11/17 runtimes allow you to use up-to-date language features and build apps that are more portable, with idiomatic code.

Understanding your migration options

To reduce runtime migration effort and complexity, the App Engine standard environment allows you to access many of legacy bundled services and APIs in the Java 11/17 runtimes, such as Memcache. Your Java 11/17 app can call the bundled services APIs through the App Engine API JAR, and access most of the same functionality as on the Java 8 runtime.

You also have the option to use Google Cloud products that offer similar functionality as the legacy bundled services. These Google Cloud products provide idiomatic Cloud Client Libraries for Java. For the bundled services that are not available as separate products in Google Cloud, such as image processing, search, and messaging, you can use third-party providers or other workarounds.

To learn more about migrating to unbundled services, see Migrating from bundled services.

There are some differences in how you perform the runtime migration, based on whether you choose to use legacy bundled services:

Migrating to Java 11/17 with bundled services Migrating to Java 11/17 without bundled services
Access bundled services using the App Engine APIs JAR. Optionally, use recommended Google Cloud products or third-party services.

Use appengine-web.xml and web.xml for app configuration.

You may also need to configure additional YAML files depending on the features your app uses.

Use app.yaml for app configuration.

You may also need to configure additional YAML files depending on the features your app uses.

Apps are deployed via Jetty. Use WAR format to package your app. Apps are deployed using your own server. Use JAR format to package your app. To learn more about converting your existing WAR file to an executable JAR, see Re-packaging a WAR file.

Overview of the migration process

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/17 runtimes:

Key differences between the Java 8 and Java 11/17 runtimes

The following is a summary of the differences between the Java 8 and Java 11/17 runtimes on App Engine standard environment:

Java 8 runtime Java 11/17 runtimes
Server deployment Server deployed for you via Jetty If your app does not use the legacy bundled services, you have to deploy a server yourself.1
App Engine legacy bundled services Provided Provided
Ability to use Cloud Client Libraries for Java Yes Yes
Language extension and system library support Yes Yes
External network access Yes Yes
Files system access Read/write access to /tmp Read/write access to /tmp
Language runtime Modified for App Engine Unmodified, open-source runtime
Isolation mechanism gVisor-based container sandbox gVisor-based container sandbox
Testing with local development server Supported Supported
Thread safety configuration Can be specified in the appengine-web.xml file. Can't be specified in the configuration files. All apps are presumed to be thread safe.3
Logging Uses a java.util.logging.
ConsoleHandler, which writes to
stderr and flushes the stream
after each record.
Standard Cloud Logging 2

Notes:

  1. If your app is not using the legacy bundled services, the Java 11/17 runtimes can execute any Java framework as long as you package a web server that is configured to respond to HTTP requests on the port specified by the PORT environment variable (recommended) or on port 8080. For example, the Java 11/17 runtimes can run a Spring Boot Uber JAR as is. For more examples, see the Framework flexibility section.

    If your app is using the legacy bundled services, App Engine deploys it using Jetty in the same way as in the Java 8 runtime.

  2. Logging in the Java 11/17 runtimes follows the logging standard in Cloud Logging. In the Java 11/17 runtimes, app logs are no longer bundled with the request logs but are separated in different records. To learn more about reading and writing logs in the Java 11/17 runtimes, see the logging guide.

  3. To configure a non-threadsafe app in the Java 11/17 runtime, similar to setting <threadsafe>false</threadsafe> in Java 8, set max concurrency to 1 in either your app.yaml file, or appengine-web.xml file if using the legacy bundled services.

Framework flexibility

The Java 11/17 runtimes do not include any web-serving framework unless you are using the legacy bundled services. This means that you can use a framework other than a servlet-based framework. If you are using the legacy bundled services, the Java 11/17 runtimes provide the Jetty web-serving framework.

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

Migrating XML to YAML file formats

gcloud CLI 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 gcloud CLI 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.uc.r.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.uc.r.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>