This project supports building a GraalVM Native Image for the Jakarta EE 11 App Engine runtime using JDK 25.
- GraalVM JDK 25: Download and install GraalVM with JDK 25 support.
- Native Image: Ensure the
native-imagetool is available in your GraalVM distribution. - Maven: Standard Maven build environment.
jakarta_apis/pom.xml: Contains thenativeprofile which automates the build and test process.jakarta_apis/build_native.sh: The core script for generating reflection/resource config, building the image, and preparing deployment.jakarta_apis/cloudbuild.yaml: Automated build and deployment for Google Cloud Build.jakarta_apis/Dockerfile: Build environment for local Docker-based Linux builds..github/workflows/graalvm-jdk25.yml: Automated CI pipeline for producing the Linux binary.
The build process is fully integrated into Maven. To build the native image locally:
-
Set your environment:
export JAVA_HOME=/path/to/graalvm-jdk-25 export PATH=$JAVA_HOME/bin:$PATH
-
Run the full build sequence:
cd jakarta_apis mvn clean package appengine:stage install -Pnative -DskipTests
The install phase with the -Pnative profile will automatically trigger build_native.sh, which compiles and tests the binary.
If you are on macOS or Windows and want to produce a Linux-native binary, you can use Docker.
-
Build the GraalVM JDK 25 Image:
cd jakarta_apis docker build -t appengine-graal-build .
-
Run the Build Container: Mount the current directory to
/appinside the container. The final binary will be written back to your localtarget/directory.docker run --rm -v "$(pwd)":/app appengine-graal-build
After the container finishes, the binary will be at jakarta_apis/target/appengine-native-image.
You can automate the entire build and deployment process using Google Cloud Build. This process is dynamic and uses your pom.xml as the single source of truth for deployment parameters (projectId, version, promote).
-
Prerequisites:
- Install the Google Cloud SDK.
- Ensure the Cloud Build and App Engine Admin APIs are enabled in your project.
- Authenticate and set your project:
gcloud auth login gcloud config set project [YOUR_PROJECT_ID]
-
Submit the Build: From the project root, run:
gcloud builds submit jakarta_apis --config jakarta_apis/cloudbuild.yaml
Cloud Build will automatically:
- Spin up a high-CPU build worker (40-minute timeout).
- Parse your
projectId,version, andpromotesettings frompom.xml. - Build the Linux-native binary using GraalVM JDK 25.
- Inject the native entrypoint into
app.yaml. - Deploy the resulting binary to App Engine using the extracted parameters.
- Staging: The
appengine:stagecommand generatestarget/appengine-staging/WEB-INF/quickstart-web.xml. This file contains a pre-scanned list of all servlets, initializers, and JSP classes. - Deep Reflection Discovery:
build_native.shuses a multi-layered discovery process to build thereflect-config.json:- Deep XML Parsing: Uses
perlto extract not just main classes, but also classes hidden ininterested,applicable, andannotatedarrays within the JettyContainerInitializers. - Full Application Discovery: Automatically extracts every class from the essential runtime jars, the App Engine SDK, and all application dependency jars and classes in the staging directory.
- Legacy Filtering: Automatically filters out classes related to
.ee8.andjavax.servlet.namespaces, as well as specific legacy utility classes. - Full Access Registration: All discovered classes are registered with
allDeclaredConstructors,allDeclaredMethods, andallDeclaredFieldsset totrue.
- Deep XML Parsing: Uses
- Resource Discovery: Automatically scans all jars and class directories for
.xml,.properties,.dtd,.xsd, and.txtfiles. Each resource is registered with and without a leading slash to ensure compatibility with various resource loading mechanisms. - Runtime Assembly: The script unpacks the
runtime-deploymentzip and keeps only the 3 essential jars:runtime-main.jar,runtime-impl-jetty121.jar, andruntime-shared-jetty121-ee11.jar. - Hybrid Initialization: GraalVM's
native-imageuses a targeted strategy:- Build-Time: Core utilities (
org.slf4j), networking metadata (sun.net), Jetty, and Protobuf descriptors are frozen for maximum performance. - Run-Time: Complex dynamic middleware (problematic Protobuf descriptors, App Engine Search/Datastore API internals, and thread management) are deferred to ensure correctness.
- Build-Time: Core utilities (
- Verification: The script automatically starts the binary with
GAE_PARTITION=devand verifies that it reaches the "JavaRuntime starting..." state without fatal errors. - Deployment Preparation: The script parses
pom.xmlto extract deployment settings and generates atarget/appengine-staging/deploy.shscript. It also injects the native entrypoint into the stagedapp.yaml.
The resulting binary (appengine-native-image) is a standalone Linux executable.
- Environment Variables: The runtime environment provides variables like
PORTandGAE_PARTITION. - Heap Management: The binary respects standard flags like
-Xmx. By default, App Engine Java runtimes target a heap size of ~80% of available instance memory. - Execution: To run manually in a production-like environment:
./appengine-native-image -Xmx512m --fixed_application_path=/path/to/staged/app /path/to/runtime
- Grep on macOS: The script uses
perlfor pattern extraction to avoid incompatibilities with the default macOSgrep. - Cloud SDK hang: The
appengine-maven-pluginis configured to use a localcloudSdkHomeand hasdownloadCloudSdkset tofalse. - Classpath Errors: If
ClassNotFoundExceptionoccurs, ensure the class is added to theALL_CLASSES_WITH_RUNTIMElist inbuild_native.sh.
The App Engine runtime itself is now fully configured. Any remaining tasks are web application specific. If your app uses additional libraries (like Hibernate or Jackson), you may need to add those classes to the reflection configuration in build_native.sh.