Skip to content

feature: add GraalVM native image build support for seata-server - #8162

Open
xuxiaowei-com-cn wants to merge 127 commits into
apache:2.xfrom
xuxiaowei-com-cn:xuxiaowei/Seata-Server-GraalVM
Open

feature: add GraalVM native image build support for seata-server#8162
xuxiaowei-com-cn wants to merge 127 commits into
apache:2.xfrom
xuxiaowei-com-cn:xuxiaowei/Seata-Server-GraalVM

Conversation

@xuxiaowei-com-cn

@xuxiaowei-com-cn xuxiaowei-com-cn commented Jul 9, 2026

Copy link
Copy Markdown
Member

Ⅰ. Describe what this PR did

This PR adds GraalVM Native Image packaging support to the Seata Server, enabling users to build and deploy the Seata Server as a self-contained native binary with sub-second startup time, lower memory footprint, and no JDK dependency at runtime.

Changes Overview (14 files, +3636 / −105)

1. GraalVM Native Image Build Profiles (server/pom.xml)

  • Added native Maven profile that configures spring-boot-maven-plugin (AOT processing) and native-maven-plugin (GraalVM native image compilation).
  • Added nativeTest profile for AOT-based native testing with junit-platform-launcher.
  • Note: The OS/arch auto-activation profiles (native-linux-amd64, native-linux-aarch64, native-darwin-x86_64, native-darwin-aarch64, native-windows-amd64) that set the native.platform classifier were later promoted to the root pom.xml so the property is available to all submodules.
  • Configured build-time initialization for logback, slf4j, fastjson2 and run-time initialization for netty.channel.kqueue.
  • Set mainClass to org.apache.seata.server.ServerApplication.
  • Added Spring-Boot-Native-Processed: true manifest entry.
  • Excluded spring-boot-devtools from native compilation.

2. GraalVM Reachability Metadata (3 new config files)

  • Added reflect-config.json (~2801 lines) — registers classes, methods, and fields for reflective access at native image build time, covering Seata's core components (serializers, codecs, RPC handlers, store managers, configuration providers, discovery providers, etc.).
  • Added resource-config.json (~463 lines) — registers resource bundles and configuration files (Spring factories, SPI service descriptors, SQL scripts, configuration templates) for inclusion in the native image.
  • Added proxy-config.json — registers dynamic proxy interfaces used by Seata (e.g., Spring AOP proxies, configuration binding interfaces).

All metadata files are placed under META-INF/native-image/org.apache.seata/seata-server/ for GraalVM 25 compatibility.

3. CI/CD: Multi-Platform Native Build Workflow (.github/workflows/native.yml)

  • Added a new GitHub Actions workflow (Native Build) that builds GraalVM native images for the Seata Server across 5 platforms: ubuntu-24.04 (amd64), ubuntu-24.04-arm (arm64), macos-26-intel (x86_64), macos-26 (apple silicon), windows-latest (amd64).
  • Uses GraalVM JDK 25 distribution via actions/setup-java@v5.5.0.
  • Includes Maven repository caching (actions/cache/restore@v4 / actions/cache/save@v4) with SNAPSHOT cleanup.
  • Uploads native binaries as workflow artifacts via actions/upload-artifact@v7.0.1.
  • Triggers on push and PR to 2.x, develop, master branches (ignores markdown-only changes).

4. logback-spring.xml Simplification (GraalVM Compatibility)

  • Removed all Janino <if> conditional logic (e.g., <if condition='property("LOGSTASH_APPENDER_ENABLED").equals("true")'>) because Janino's dynamic bytecode compilation is not supported in GraalVM native images.
  • Simplified the configuration to always include console-appender and file-appender by default.
  • Removed the LOG_BASH_DIR external directory path logic.
  • Conditional appenders (logstash, kafka, metric) can still be enabled by uncommenting the corresponding <appender-ref> elements — these are documented with inline comments.

5. Spring AOT Compatibility: @Resource@Autowired Setter Injection

  • AbstractSeataInstanceStrategy: Replaced @Resource field injection with @Autowired setter-based injection for registryProperties, serverProperties, and registryNamingServerProperties. Removed ApplicationContext dependency and @PostConstruct initialization — properties are now injected directly via setters, which is compatible with Spring AOT's closed-world analysis.
  • ServerInstanceStrategyConfig: Changed seataInstanceStrategy() bean method to accept dependencies via constructor parameters and pass them to the strategy via setters, eliminating the implicit ApplicationContext.getBean() lookup.
  • SpringBootConfigurationProvider: Adjusted for AOT compatibility.

6. Build Infrastructure (build/pom.xml, root pom.xml, Makefile)

  • build/pom.xml:
  • Added native-maven-plugin version (1.1.3) to pluginManagement.
  • Added default native.platform property (promoted from server/pom.xml) so it is available to all submodules for GraalVM native-image builds.
  • Root pom.xml:
  • Added Spotless Maven plugin configuration for JSON formatting of native-image metadata files.
  • Added OS/arch auto-activation profiles (native-linux-*, native-darwin-*, native-windows-*) — promoted from server/pom.xml so the native.platform property is available to all submodules for GraalVM native-image builds.
  • Makefile:
  • Added package-server-native-pre, package-server-native, package-server-native-only targets for building native images.
  • Added spotless-check and spotless-apply targets.
  • Added -e flag to all Maven commands for consistent error output.
  • Widened help text column width.

7. Dependency Update

  • Upgraded zstd-jni from 1.5.0-4 to 1.5.7-3 (dependencies/pom.xml).

8. Test Adjustment

  • Disabled AppenderTest (@Disabled) due to the logback configuration simplification that removed Janino-based conditional appender logic.

Usage

Regenerate GraalVM Reachability Metadata (e.g., after adding new components that use reflection):

  1. Run the Seata Server with the Native Image Agent to generate metadata into a temporary directory:

    # Ensure GRAALVM_HOME points to your GraalVM installation directory
    $GRAALVM_HOME/bin/java -agentlib:native-image-agent=config-output-dir=./target/native-image-config -jar ./server/target/seata-server.jar
    # By default, the server starts in `file` config/registry mode, and the agent
    # will only capture reachability metadata for that mode. To generate metadata
    # for other modes, set the `seata.config.type` and/or `seata.registry.type`
    # properties:
    #
    #   seata.config.type=nacos
    #   seata.config.type=consul
    #   seata.config.type=apollo
    #   seata.config.type=zk
    #   seata.config.type=etcd3
    #
    #   seata.registry.type=nacos
    #   seata.registry.type=eureka
    #   seata.registry.type=redis
    #   seata.registry.type=zk
    #   seata.registry.type=consul
    #   seata.registry.type=etcd3
    #   seata.registry.type=sofa
    #   seata.registry.type=seata
    #
    # Example — generate metadata for nacos config + registry:
    #   $GRAALVM_HOME/bin/java -agentlib:native-image-agent=config-output-dir=./target/native-image-config \
    #     -jar ./server/target/seata-server.jar \
    #     --seata.config.type=nacos --seata.registry.type=nacos
  2. Update the generated metadata into the project resource directory: server/src/main/resources/META-INF/native-image/org.apache.seata/seata-server/

    Note: The native-image-agent generates a single merged JSON file (e.g., reachability-metadata.json) in the config-output-dir. This file contains all reachability metadata in one place. It must be split into the following separate configuration files before committing to the project.

    Important: The JSON file is only written to disk after the application terminates normally (i.e., after a graceful shutdown, not a forced kill). You must exercise all relevant code paths (config loading, service registration, transaction coordination, serialization, etc.) during the run, then stop the server cleanly (e.g., Ctrl+C or kill -TERM) to trigger the agent to flush and write the metadata output.

    ⚠️ Important: All business flows must be exercised during the agent run; otherwise the generated metadata will be incomplete, which may cause certain business flows to fail at runtime.

    File Content
    reflect-config.json Classes, methods, and fields registered for reflective access (e.g., serializers, codecs, RPC handlers, store managers, configuration/discovery providers)
    resource-config.json Resource bundles and configuration files for inclusion in the native image (e.g., Spring factories, SPI service descriptors, SQL scripts, configuration templates)
    serialization-config.json Classes registered for Java serialization
    proxy-config.json Dynamic proxy interfaces (e.g., Spring AOP proxies, configuration binding interfaces)
    jni-config.json JNI-related configuration (if applicable; may be empty if no JNI calls are used)

    Use the top-level JSON keys in the generated file to identify which entries belong to each category, then distribute them into the corresponding files.

  3. Compile the Native Image

Build the Native Image:

# One-step: build all dependencies and compile native image
make package-server-native

# Or manually:
mvn clean install -DskipTests -pl server -am
mvn clean package -DskipTests -pl server -Pnative spring-boot:process-aot native:compile

Run the native binary (no JDK required):

./server/target/seata-server-{version}-{platform}

Ⅱ. Does this pull request fix one issue?

#8161

This PR implements the feature proposal described in issue #8137 (Spring Boot 4 upgrade) follow-up — adding GraalVM Native Image support to the Seata Server for improved deployment efficiency and cloud-native compatibility.

Ⅲ. Why don't you add test cases (unit test/integration test)?

  • The native image build itself serves as an integration test — the CI workflow (native.yml) builds native binaries on 5 platforms on every PR/push, and a successful native image compilation validates the GraalVM reachability metadata and AOT compatibility.
  • AppenderTest has been disabled because it tested Janino <if> conditional logic in logback-spring.xml, which was removed for GraalVM compatibility. The logback appender behavior (console, file) is implicitly verified by the native image build succeeding and the server starting correctly.
  • A nativeTest Maven profile is provided for future AOT-based native testing using junit-platform-launcher.
  • Config, Registry & Store Mode Tests: The following test matrix tracks verification of each config/registry/store mode against the GraalVM native image. These are follow-up tasks to ensure full feature parity as described in "Phased approach" (Phase 2+).

Config Mode Tests

Config Mode Manual Test CI Test Notes
file ✅ Done ✅ Done Default mode, verified via CI native build
nacos ✅ Done ⬜ TODO Requires Nacos server
consul ⬜ TODO ⬜ TODO Requires Consul server
apollo ⬜ TODO ⬜ TODO Requires Apollo server
zk ⬜ TODO ⬜ TODO Requires ZooKeeper server
etcd3 ⬜ TODO ⬜ TODO Requires Etcd3 server

Registry Mode Tests

Registry Mode Manual Test CI Test Notes
file ✅ Done ✅ Done Default mode, verified via CI native build
nacos ✅ Done ⬜ TODO Requires Nacos server
eureka ⬜ TODO ⬜ TODO Requires Eureka server
redis ⬜ TODO ⬜ TODO Requires Redis server
zk ⬜ TODO ⬜ TODO Requires ZooKeeper server
consul ⬜ TODO ⬜ TODO Requires Consul server
etcd3 ⬜ TODO ⬜ TODO Requires Etcd3 server
sofa ⬜ TODO ⬜ TODO Requires SOFARegistry server
seata ⬜ TODO ⬜ TODO Requires Seata registry server

Store Mode Tests

Store Mode Manual Test CI Test Notes
file ✅ Done ✅ Done Default mode, verified via CI native build
db ⬜ TODO ⬜ TODO Requires database (MySQL/PostgreSQL/etc.)
redis ⬜ TODO ⬜ TODO Requires Redis server
raft ⬜ TODO ⬜ TODO Requires Seata Server cluster (Raft)

Test Plan for Each Mode

Each mode test should verify:

  1. Startup: Seata Server native image starts successfully with the target config/registry/store mode.
  2. Configuration Loading (config modes): Configuration properties are correctly loaded from the remote config center.
  3. Service Registration (registry modes): Seata Server registers itself to the registry center and is discoverable by clients.
  4. Service Discovery (registry modes): Seata Server can discover other registered services.
  5. Session Persistence (store modes): Transaction session data (global sessions, branch sessions) is correctly persisted to and loaded from the target store.
  6. Session Query (store modes): Session data can be queried and listed from the target store.
  7. Health Check / Heartbeat: The registered service maintains heartbeat and is not evicted.
  8. Graceful Shutdown: Service is deregistered on normal shutdown.
  9. GraalVM Reachability: No ClassNotFoundException, NoSuchMethodException, or reflection errors at runtime for the mode-specific components.

Ⅳ. Describe how to verify it

Option 1: Local verification (requires GraalVM JDK 25)

# Build dependencies
mvn clean install -DskipTests -pl server -am

# Compile native image
mvn clean package -DskipTests -pl server -Pnative spring-boot:process-aot native:compile

# Run the native server
./server/target/seata-server-*-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')

Expected: The Seata Server starts in milliseconds (vs. seconds in JVM mode) and is ready to accept transactions.

Option 2: CI verification

Check the Native Build workflow results on this PR — it builds native images on ubuntu-24.04 (amd64), ubuntu-24.04-arm (arm64), macos-26-intel, macos-26 (apple silicon), and windows-latest.

Ⅴ. Special notes for reviews

  1. GraalVM reachability metadata (reflect-config.json, resource-config.json, proxy-config.json): These files were generated through iterative native image builds and testing. They register all classes, methods, resources, and proxies that Seata accesses via reflection at runtime. Reviewers should focus on whether any critical Seata components are missing from these configs.

  2. Injection style change: @Resource field injection was replaced with @Autowired setter/constructor injection in AbstractSeataInstanceStrategy and ServerInstanceStrategyConfig. This is required because Spring AOT's closed-world analysis cannot resolve @Resource-injected fields during native compilation. The runtime behavior is identical.

  3. logback-spring.xml breaking change: The removal of Janino <if> conditions means that LOGSTASH_APPENDER_ENABLED, KAFKA_APPENDER_ENABLED, and METRIC_APPENDER_ENABLED properties no longer dynamically control appender inclusion at runtime. Users who need these appenders must manually uncomment the corresponding <appender-ref> elements in logback-spring.xml. This is a known limitation of GraalVM native images (no runtime bytecode generation). A follow-up PR could add programmatic appender registration via Spring's @Conditional beans.

  4. Phased approach: This PR focuses on Phase 1 — basic server startup, configuration loading, and core transaction coordination in native mode. Full feature parity with JVM mode (all serializers, all store modes, all discovery modes) will be addressed in follow-up PRs as testing coverage expands.

  5. PGO (Profile-Guided Optimization): Not included in this PR. Can be added later to further improve native image runtime performance.

- Add native-maven-plugin version management in build/pom.xml
- Add native and nativeTest profiles in server/pom.xml for GraalVM AOT compilation
- Add Makefile targets: package-server-native, package-server-native-pre, package-server-native-only
…5.5.0 in native workflow

1. actions/setup-java@v3.12.0: No supported distribution was found for input graalvm
2. Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: actions/checkout@v3, actions/setup-java@v3.12.0. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
- Add ubuntu-latest-arm to native build matrix alongside ubuntu-latest
- Update step names to include OS identifier for clarity
- Add -am flag to Makefile package-server-native-pre target to also build server module dependencies
…runners

Add matrix.os to concurrency group and cancel-in-progress to avoid
ubuntu-latest and ubuntu-latest-arm builds cancelling each other.
Comment out concurrency group and cancel-in-progress to avoid
interference between multiple native build workflow runs.
Replace ubuntu-latest with ubuntu-24.04 for more reproducible builds.
Add macos-26-intel, macos-26-large, and windows-latest to the
native build CI matrix alongside existing Linux runners.
…ve profiles

Add --initialize-at-run-time=io.netty.channel.kqueue to both
native and nativeTest profiles to fix GraalVM native image
issues related to Netty's kqueue transport on non-macOS platforms.
- Add OS/arch-specific Maven profiles (linux-amd64, linux-arm64, darwin-amd64,
  darwin-arm64, windows-amd64) to set native.platform property
- Configure native-maven-plugin imageName to
  seata-server-{version}-{platform}, e.g. seata-server-2.8.0-SNAPSHOT-linux-amd64
- Add upload-artifact step to native CI workflow
- Exclude .jar files from uploads to only ship the native binary
…server

$GRAALVM_HOME/bin/java -agentlib:native-image-agent=config-output-dir=./target/native-image-config -jar ./server/target/seata-server.jar
- reflect-config.json: rename 'type' to 'name' key per GraalVM 25 schema
- reflect-config.json: remove deprecated 'jniAccessible' attribute
- reflect-config.json: extract proxy entries to separate proxy-config.json
- resource-config.json: convert array to object format with 'resources.includes'
- resource-config.json: rename 'glob' to 'pattern' key
- server/pom.xml: add --initialize-at-build-time=com.alibaba.fastjson2
Add Spotless Jackson JSON formatter in pom.xml and reformat
proxy-config.json, reflect-config.json, resource-config.json
Add constructor and method entries for Apollo config classes
(ConfigPropertySourceFactory, PlaceholderHelper, SpringValueRegistry),
Iterable.iterator(), Iterator.hasNext()/next(), and comprehensive
StringBuilder methods to ensure proper reflection access in native image.
…H_DIR logic

Remove the if/else branch that switched between resource and file
includes based on LOG_BASH_DIR property. Unify to always use resource
includes. Also remove conditional appender-refs for logstash, kafka,
and metric appenders, replacing them with comments.

logback-spring.xml: remove <if> conditionals that require janino
runtime compilation (not supported in native image)
… and fix formatting

- Add Apollo Guice FastClassByGuice entries (ConfigPropertySourceFactory,
  PlaceholderHelper, SpringValueRegistry) with GUICE$INVOKERS fields to
  reachability-metadata.json for GraalVM native image support
- Include test-native-server profile in Makefile spotless-apply target
- Remove trailing blank line in HealthRestController.java
…inates subdirectory

Move reachability-metadata.json from:
  META-INF/native-image/org.apache.seata/seata-server/
to:
  META-INF/native-image/

This simplifies the directory structure and aligns with GraalVM's
flat metadata lookup convention. All references are updated:
- pom.xml: Spotless JSON include pattern simplified
- Makefile: spotless-apply includes test-native-metadata-merge profile
- ExecuteMergeNativeImageMetadataTests: target path updated
… separate native config

Introduce logback-spring-jvm.xml (with <if> conditional and LOG_BASH_DIR
support) for the JVM distribution, while keeping the default
logback-spring.xml simplified for GraalVM native image compatibility.

- Add logback-spring-jvm.xml: restored <if> conditional that loads
  appender includes from either classpath or external conf directory
  based on LOG_BASH_DIR, plus conditional logstash/kafka/metric
  appender support (requires Janino runtime compilation)
- Update logback-spring.xml: add comment documenting the JVM/native
  split and distribution packaging behavior
- Update distribution/release-seata.xml: use logback-spring-jvm.xml as
  source, renamed to logback-spring.xml in conf/ so the existing launch
  script picks up the JVM variant with external conf support

This fixes the JVM behavior regression where modifications to external
conf/logback/console-appender.xml and conf/logback/file-appender.xml
were ignored after the GraalVM optimization removed conditional logic.
…native image

Add NacosPayloadRegistryInitializer to pre-populate PayloadRegistry since
Reflections classpath scanning is unavailable in GraalVM native images,
preventing 'Unknown payload type' errors during Nacos gRPC communication.

Introduce NACOS_MODE_ENV macro and separate Makefile targets:
- run-server-native-file-metadata / run-server-native-file-mode
- run-server-native-nacos-metadata / run-server-native-nacos-mode

Enrich reachability-metadata.json with Nacos gRPC, Netty, protobuf,
and SPI service entries required for native image compilation.
…-GraalVM

# Conflicts:
#	Makefile
#	changes/en-us/2.x.md
#	changes/zh-cn/2.x.md
#	pom.xml
#	server/pom.xml
#	test-suite/test-native-metadata-merge/src/test/java/org/apache/seata/metadata/ExecuteMergeNativeImageMetadataTests.java
…ignore

Delete the standalone MergeNativeImageConfig.java tool that was used
to merge reachability-metadata.json into native-image config files.
Add __pycache__/ entry to .gitignore for Python cache directories.
@xuxiaowei-com-cn xuxiaowei-com-cn linked an issue Aug 11, 2026 that may be closed by this pull request
2 tasks
… mode in Makefile

- Rename run-server-native-jar to run-server-native-file-jar and add explicit
  SEATA_CONFIG_TYPE/REGISTRY_TYPE/STORE_TYPE=file env vars
- Add run-server-native-nacos-jar target with NACOS_MODE_ENV macro for
  config(nacos)+registry(nacos)+store(file) mode
- Add install-server-native and package-server-native targets for building
  seata-server GraalVM native image
- Add run-server-native-file and run-server-native-nacos targets for running
  the compiled native image in file and nacos modes
- Add NACOS_MODE_ENV macro to consolidate Nacos config+registry env vars
- Add NACOS_NAMESPACE, NACOS_GROUP, NACOS_DATAID, NACOS_USERNAME, NACOS_PASSWORD
  variables with sensible defaults
- Change NATIVE_PLATFORM to conditional assignment (?=) for external override
Normalize FastClassByGuice dynamic numeric suffixes during merge key
matching so entries with different hash suffixes are recognized as the
same logical element. Target values are preserved to avoid meaningless
metadata churn from transient suffix changes.

- Add normalizeType() to strip variable numeric suffix from
  FastClassByGuice type names for stable identity keys
- Remove 9 accumulated duplicate FastClassByGuice entries from
  reachability-metadata.json (3 base classes x 4 versions -> 3)
- Add tests for deduplication and Spring CGLIB non-interference
Move JAR cleanup from upload/verify steps into the compile step,
removing duplicated rm commands and upload-artifact exclude rules.
…Java FFM API

- Add NativeLibraryUtil for Nacos shaded Netty native library loading
- Add java.lang.foreign types (Linker, SymbolLookup, ValueLayout, etc.)
- Add Foreign Function & Memory API downcalls metadata
- Add Netty epoll native .so resources for Nacos shaded gRPC
- When an object with a recognized key is appended, register the key
  so subsequent source elements with the same key merge instead of
  duplicating.
- For objects without a recognizable key (e.g. foreign.downcalls
  entries), use containsNode to skip already-present elements.
- Rename build job to 'build & test (file)' to reflect file config mode
- Add new 'test (nacos)' job to verify native binary with nacos config mode
- Nacos job runs on ubuntu-24.04 / ubuntu-24.04-arm with Docker MySQL
Replace the extensive per-module path list with a single targeted path
'server/src/main/resources/META-INF/native-image/**' since that directory
is the key trigger for native image build changes.
The nacos job downloads the native binary artifact from the build job,
so it must depend on build completing first.
…nacos job

- Remove needs: build dependency so nacos job can start independently
- Replace actions/download-artifact with gh run download loop
- Add MAX_RETRIES (60) and SLEEP_SECONDS (30) env vars for polling
- Retry for up to 30 minutes waiting for artifact availability
…n nacos job

- Add 'Wait for build job' step that polls gh api to check build job status
- Replace gh run download polling with actions/download-artifact
- Move MAX_RETRIES and SLEEP_SECONDS to job-level env with github expression syntax
Increase MAX_RETRIES to 180 for Windows runners to account for slower startup times.
download-artifact does not preserve execute permissions, causing Permission denied when running the native binary.
Start nacos/nacos-server:v3.2.3 container with standalone mode and custom auth configuration for integration testing.
Remove jar files from server/target to avoid conflicts with downloaded native binary artifacts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Do Not Merge Do not merge into develop module/server server module type: feature Category issues or prs related to feature request. type:native GraalVM Native Image

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Provide GraalVM Native Image packaging for Seata Server

3 participants