AWS Graviton support for Java in AWS Transform continuous modernization

Post Syndicated from Harvinder Nagpal original https://aws.amazon.com/blogs/devops/aws-graviton-support-for-java-in-aws-transform-continuous-modernization/

AWS Transform continuous modernization now supports Graviton readiness assessment and remediation for Java, generally available since October 29, 2026. Use it to move Java applications to AWS Graviton-based instances. A readiness assessment finds the Arm64 compatibility issues in a repository, and a remediation skill resolves them in code, which you then validate on Arm64 and deploy.

AWS Graviton-based instances deliver better price-performance than comparable x86 instances. Teams planning a migration therefore face two questions about their estate: which applications are ready to move, and what has to change in the ones that are not.

Answering that repository by repository does not scale. The Java code itself is rarely the obstacle. What decides readiness is an application’s dependencies, and whether each one supports Arm64. Establishing that manually across an estate is slow, repetitive, and prone to error.

In this post, you learn how to analyze a Java repository for AWS Graviton readiness, remediate the findings, validate the change on Arm64, and deploy and run the result on a Graviton-based instance.

Solution overview

AWS Transform continuous modernization connects to your source provider, discovers your repositories, analyzes them for technical debt, remediates what it finds, and opens a pull request with the result. Analysis can also run on a schedule, so once your applications are running on Graviton you can keep checking them as dependencies change.

Two skills cover the Graviton migration:

Skill What it does
AWS/graviton-readiness-assessment Scans a repository for native libraries, architecture-specific code, and Arm64-incompatible transitive dependencies. Read-only. It does not modify source or project files.
AWS/java-x86-to-graviton Resolves the findings the analysis returned: updates native libraries, upgrades blocking dependencies, adds aarch64 handling, updates Dockerfiles for multi-architecture builds, and applies Graviton JVM flags.

The readiness assessment does not change your code. It produces findings, one for each Arm64 compatibility issue it identifies. Each finding carries a severity, a category, file references, and the transformation that resolves it, so you can triage a portfolio and drive remediation directly from the findings.

Continuous modernization runs in two places. Remotely, it uses infrastructure in your AWS account or infrastructure that AWS Transform manages. Locally, it runs on your own machine. For a Graviton migration, that choice decides what can be validated:

AWS Transform-managed Customer-managed (Amazon Elastic Compute Cloud (Amazon EC2) or AWS Batch) Local
Analysis Supported, nothing to provision Supported Supported
Remediation Not supported Supported (x86 only) Supported

Note: Continuous modernization currently provisions remote infrastructure in your account on the x86 architecture. A remote remediation produces the Arm64 code changes, but it cannot build or test them on Arm64 — you do that in your own Arm64 pipeline before you merge. On an Arm64 host, a local run builds on the target architecture as part of the remediation and leaves the change on disk, which is why this post runs it locally.

Step 5 in the following diagram, deploying to a Graviton-based instance, is beyond the scope of AWS Transform, whose output is the validated change and the pull request. In your environment, Step 5 is your existing Arm64 pipeline or test environment.

Steps 1 to 4 run inside AWS Transform continuous modernization; step 5, deploying to a Graviton instance, runs in your own Arm64 environment

Figure 1: Where each step runs. Steps 1–4 are AWS Transform continuous modernization. Step 5 is your own Arm64 environment

The walkthrough covers these steps:

  1. Connect your source and discover repositories.
  2. Analyze the repository for Graviton readiness.
  3. Remediate the findings.
  4. Validate the build on Arm64.
  5. Deploy to a Graviton instance and verify.
  6. Scale across your portfolio.

For the full command surface, refer to Working with continuous modernization in the AWS Transform User Guide.

Prerequisites

To follow this walkthrough, you need:

  • An AWS account with AWS Transform enabled for your user identity, which gives you access to the AWS Transform web application. For instructions, refer to Setting up AWS Transform.
  • An AWS Region where AWS Transform is available. For the current list, refer to Supported Regions for AWS Transform.
  • AWS credentials with permission to use AWS Transform.
  • The AWS Transform CLI, installed with the setup script, and Node.js 20 or later. Install Node.js from the Node.js website, because some package managers ship an older version.
  • Git, used for version control during the transformation.
  • A source connection: a GitHub, GitLab, or Bitbucket personal access token with repository scope, or a local folder containing checkouts you already have. The readiness assessment is read-only, so it needs no write access to your code. Remediation needs write access only if you want it to open the pull request.
  • For remediation, an Arm64 host, such as a Graviton-based Amazon EC2 instance. A local remediation builds its changes on the machine it runs on, so running it on Arm64 validates them on the target architecture. On x86, the remediation skill produces the code changes but cannot build or test them on Arm64.
  • Java 8 or later. These capabilities do not change Java versions. If you also need a version upgrade, run the AWS/java-version-upgrade transformation first. For more information, refer to the AWS Transform User Guide.

This post uses Amazon Corretto 21, Gradle 8.13, and a c7g.2xlarge instance running Amazon Linux 2023.

The sample application

This walkthrough uses TASSEL, an open source genetics toolkit. It is a Gradle project on Java 21 of about 245,000 lines of code, and it is a useful example because its dependency on x86 is typical rather than exotic.

TASSEL’s inner loop is matrix algebra, and it delegates that work to a native BLAS library through a Java Native Interface (JNI) bridge. It also reads and writes scientific data formats through libraries that ship compiled binaries inside their JAR files. Those binaries are built per architecture, and several of them have no Arm64 build — which is exactly what the readiness assessment is looking for.

If you do not have a sample application of your own, clone this one into the folder you will register as a source:

git clone https://github.com/btmonier/tassel-renovate.git

Connect your source and discover repositories

In this step, you register where your code lives and list the repositories that AWS Transform can see.

To connect a source and discover repositories

  1. Open a terminal and set the Region for the session with export AWS_REGION=us-east-1.
  2. Enter atx ct status to confirm the CLI reaches the service. It prints a summary of the sources, repositories, analyses, and findings in your account.
  3. Register a hosted source with atx ct source add --name graviton-demo --provider github --org my-org --token "$PAT", or a local folder with atx ct source add --name graviton-demo --provider local --path ~/code.
  4. Enter atx ct discovery scan --source graviton-demo to discover the repositories.
  5. Enter atx ct repository list --source graviton-demo --language java to confirm what was found.

Analyze the repository for Graviton readiness

In this step, you run the readiness assessment and review what it found.

To run the analysis

  1. Enter the following command:
    atx ct analysis run --type graviton-readiness \
    --source graviton-demo --repo tassel-renovate

  2. Enter atx ct analysis get --id <analysis-id> to follow progress.
  3. Enter atx ct findings count --by severity --repo graviton-demo::tassel-renovate to see the severity breakdown. For this repository it returned seven findings: three high, three medium, and one low.

You can also run the analysis from the AWS Transform web application.

To run the analysis on the console

  1. On the Analyses tab, choose Run new analysis.
  2. For Analysis type, choose Graviton readiness.
  3. For Analysis name, enter a name for the run.
  4. For Connected source, select your source.
  5. Choose Next to select repositories and an infrastructure mode, then choose Run. To run it on a recurring cadence, choose Schedule.
The Analysis type list in the AWS Transform web application, with Graviton readiness selected

Figure 2: Graviton readiness in the Analysis type list

The Run new analysis page configured for a Graviton readiness analysis

Figure 3: Configuring a Graviton readiness analysis against a connected source

What the analysis found

The analysis returned seven findings for this repository. The following examples are two of the seven, chosen because the difference between them is why this analysis is worth running. It also reported build configuration and JVM flag issues not shown here.

The application’s own code – TASSEL loads a native BLAS bridge with System.loadLibrary("TasselBlas") and declares six native methods. The JNI glue is in the repository as C source, with no compiled binary for Arm64.

Inside the dependency JAR files – This group is visible neither in your source nor in your dependency declarations. The assessment resolved the dependency tree and inspected the binaries inside each archive:

$ unzip -l cisd-jhdf5-19.04.1.jar | grep native
native/jhdf5/amd64-Linux/libjhdf5.so
native/jhdf5/arm-Linux/libjhdf5.so

$ file native/jhdf5/*/libjhdf5.so
amd64-Linux: ELF 64-bit LSB shared object, x86-64
arm-Linux: ELF 32-bit LSB shared object, ARM, EABI5

Note the second directory: it is 32-bit ARM, not aarch64. In a listing it reads as ARM support, but on Graviton the architecture-keyed loader finds no match, and no published release contains an aarch64-Linux directory. Two further dependencies arrived the same way: cisd:base:18.09.0 through jhdf5, and gov.nih.nlm.ncbi:ngs-java:2.9.0 through com.github.samtools:htsjdk:2.24.1, whose loader publishes linux-x86_64 only.

Remediate the findings

In this step, you hand the findings to the remediation skill and review the change it produces.

To remediate the findings

  1. Collect the finding IDs as the comma-separated list that --ids expects:
    atx ct findings list --repo graviton-demo::tassel-renovate --json \
    | jq -r '[.[].id] | join(",")'

  2. Pass that list to the remediation:
    atx ct remediation create --ids <finding-ids> --local

  3. Enter atx ct remediation status --id <remediation-id> to follow it.
  4. Review the branch it produced with git log and git diff --stat.

Note what you do not pass: neither the transformation nor the repository. Each finding already records the transformation that resolves it, so the remediation reads that from the findings and derives the target repositories from them. Pass --transformation-name only to pin or override that choice.

The run produced three atomic commits (the analysis reports, the change itself, then the validation reports):

build.gradle.kts | 53 ++++-
.../matrixalgebra/Matrix/DoubleMatrixFactory.java | 7 +-
graviton-analysis/ (8 report files)
graviton-validation/ (6 report files)
15 files changed, 867 insertions(+), 3 deletions(-)

Of the 867 lines added, only two files are code that runs. The remainder documents what was found and what was done.

What the remediation changed

1. It removed the x86-only dependency from the resolved classpath. The ngs-java artifact downloads linux-x86_64 native libraries at runtime and has no aarch64 search path. There is no aarch64-capable ngs-java 2.x to upgrade htsjdk to, so the remediation excluded the unused code path and recorded why:

// gov.nih.nlm.ncbi:ngs-java (transitive via htsjdk) downloads the NCBI
// ngs-sdk/ncbi-vdb natives at runtime. Those are linux-x86_64 only.
// TASSEL only uses htsjdk's pure-Java SAM/BAM/CRAM/VCF readers, so the
// unused SRA path is excluded rather than bumping htsjdk (no
// aarch64-capable ngs-java 2.x exists).
configurations.all {
    exclude(group = "openchart", module = "openchart")
    exclude(group = "gov.nih.nlm.ncbi", module = "ngs-java")
}

It then verified the exclusion took effect:

$ ./gradlew dependencies --configuration runtimeClasspath | grep -c ngs-java
0

2. It added an aarch64 native library path, ahead of the x86 payloads bundled in the JAR files. No aarch64 build of jhdf5 or cisd:base is published, and their C sources are not in the repository, so they cannot be recompiled there. The remediation added a resolution order for externally built binaries:

val tasselNativeLibDir: String =
    (project.findProperty("tasselNativeLibDir") as String?)
        ?: System.getenv("TASSEL_NATIVE_LIB_DIR")
        ?: file("src/main/native/aarch64").absolutePath

val isAarch64: Boolean = System.getProperty("os.arch").lowercase().let {
    it == "aarch64" || it == "arm64"
}

3. It applied Graviton JVM flags and wired the native path into the runtime. The test block previously set only -Xmx10g:

test {
    // -Xmx10g is architecture-neutral. The code-cache sizing flags below are the
    // Graviton (ARM64) tuning recommended for JDK 21; both are valid on x86-64 too.
    jvmArgs = listOf<String>(
        "-Xmx10g",
        "-XX:ReservedCodeCacheSize=64M",
        "-XX:InitialCodeCacheSize=64M"
    )
    if (isAarch64 && file(tasselNativeLibDir).isDirectory) {
        systemProperty("java.library.path", tasselNativeLibDir)
        systemProperty("native.libpath", tasselNativeLibDir)
    }

4. It made the state of the native libraries visible at build and run time. The build now warns on an Arm64 host when the aarch64 directory is missing, naming the libraries to supply. At the native load site, which previously logged the same message on every platform, the architecture is now included:

-	myLogger.info("TasselBlas library for system-specific BLAS/LAPACK not found. "
-			+ "Using system-independent EJML for DoubleMatrix operations.");
+	myLogger.info("TasselBlas library for system-specific BLAS/LAPACK not found for os.arch="
+			+ osArch + ". Using system-independent EJML for DoubleMatrix operations. To use"
+			+ " native BLAS on this architecture, place a matching libTasselBlas.so on"
+			+ " java.library.path.");

Together, these changes give the project a defined place for aarch64 binaries ahead of the bundled x86 ones, apply the Graviton JVM tuning, and report the state of those libraries at build and run time.

Validate the build on Arm64

In this step, you confirm the change compiles on the target architecture, which the remediation does for you on an Arm64 host.

To validate the build

  1. Check out the branch the remediation created.
  2. Enter ./gradlew clean build -x test --no-daemon.
  3. Confirm the output reports a successful build and produces the JAR file.
    BUILD SUCCESSFUL in 14s
    5 actionable tasks: 5 executed
    
    $ ls -la build/libs/sTASSEL.jar
    -rw-r--r-- 3681163 build/libs/sTASSEL.jar
    
    $ ls build/libs/lib/*.jar | wc -l
    61

The JAR file is produced and 61 dependency JAR files resolve.

Deploy to a Graviton instance and verify

This step is outside AWS Transform, as Figure 1 shows. Arm64 is not a single platform: macOS and Linux on aarch64 differ in exactly the dimension that matters here, which native binaries exist. The remediation ran locally, so the change is already on disk and ready to carry onto an instance.

To deploy and verify on Graviton

  1. Launch a c7g.2xlarge instance running Amazon Linux 2023.
  2. Install Amazon Corretto 21, OpenBLAS, and LAPACK as aarch64 packages.
  3. Copy the remediated repository to the instance.
  4. Build it on the instance with ./gradlew classes copyDependencies.
  5. Compile the native BLAS bridge for aarch64. Its C source includes a macOS-only header, so first change #include <vecLib/cblas.h> to #include <cblas.h>, then run:
    gcc -O3 -shared -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" \
    -I/usr/include/openblas -o libTasselBlas.so <jni-source>.c \
    -lopenblas -llapack -lm

  6. Run the application and confirm the native library loads.

The build resolves 62 dependency JAR files on the instance. The file command confirms the native library is built for the target architecture:

libTasselBlas.so: ELF 64-bit LSB shared object, ARM aarch64

Running the application on the instance:

os.arch : aarch64
java.vendor : Amazon.com Inc. (Corretto 21)
libTasselBlas : LOADED
DoubleMatrix backend: blas
fast path active : YES (native BLAS/LAPACK)

matmul 512x512 best 12.47 ms | 21.53 GFLOP/s

The native library loads, the BLAS backend is selected, and the matrix algebra runs. The application works on Graviton: the same application and the same JVM, now on Arm64.

It is also faster. In our testing on this c7g.2xlarge instance, the same operation took 60.36 ms on the pure-Java path and 12.47 ms with the native library, roughly five times the throughput. Those two numbers compare the native and pure-Java paths on Graviton rather than Graviton against x86. They are the reason to rebuild the native libraries rather than work around them.

Scale across your portfolio

In this step, you extend the same analysis across a source and put it on a cadence, using AWS Transform-managed infrastructure.

To analyze a portfolio on a recurring schedule

  1. Analyze every repository in a source:
    atx ct remote analysis --type graviton-readiness --mode aws-managed \
    --sources graviton-demo

  2. Create a schedule so the analysis repeats:
    atx ct schedule create --name graviton-weekly --mode aws-managed \
    --execution-role <role-arn> --recurrence weekly:MONDAY \
    --type graviton-readiness --sources graviton-demo

A single submission can include up to 5,000 repositories. Schedules that run on AWS Transform-managed infrastructure need an execution role that AWS Transform assumes on each run. For how to configure it, refer to Execution role for AWS Transform-managed schedules. For how analysis and remediation are billed, refer to AWS Transform pricing.

Clean up

The Graviton-based instance incurs ongoing charges while it runs, and a recurring schedule incurs charges on every run. To avoid them, terminate the instance when you are finished:

aws ec2 terminate-instances --instance-ids <instance-id>

If you created a recurring schedule, delete it with atx ct schedule delete. The source connection and the analysis and remediation records carry no ongoing cost. To remove them, use atx ct remediation delete, atx ct analysis delete, and atx ct source remove.

Conclusion

In this post, you learned how to get a Java application ready for AWS Graviton with AWS Transform continuous modernization. The readiness assessment surfaced Arm64-incompatible native libraries that were not declared anywhere in the project. The remediation skill resolved them in code, in three atomic commits touching two files, and validated the build on Arm64. You then deployed the result to a Graviton instance yourself, where the application ran using its native BLAS library.

The readiness assessment is read-only, so you can point it at all the repositories within your source and see where your estate stands before changing anything. To get started, open the AWS Transform console and run a Graviton readiness analysis, or install the CLI and run the commands in this post.

For more information about the command surface, refer to Working with continuous modernization in the AWS Transform User Guide. For instructions on getting started with AWS Transform, refer to Setting up AWS Transform. For pricing details, refer to AWS Transform pricing.

For a related walkthrough using the interactive transformation, refer to Migrating your Java applications to AWS Graviton using AWS Transform custom. For guidance on tuning workloads after you migrate, refer to the AWS Graviton Technical Guide.

 


About the authors

Harvinder Nagpal

Harvinder Nagpal

Harvinder is a Sr. Specialist on the Agentic AI team at AWS. He focuses on the Agentic AI Developer Experience portfolio and helps customers take Agentic AI to production. He works with AWS field teams and Partners on GTM strategy and executive conversations, and he passes customer feedback to product teams. He spent 15+ years in identity, access management and cybersecurity, so he helps customers build security, governance and compliance into Agentic AI from the start.

Venugopalan Vasudevan

Venugopalan Vasudevan

Venugopalan is a Principal Specialist Solutions Architect at AWS. He leads generative AI initiatives that focus on Kiro, and AWS Transform. He helps customers adopt and scale AI-powered developer and modernization solutions to accelerate innovation.

Varun Kulkarni

Varun Kulkarni

Varun is a Senior Product Manager–Technical at AWS working on AWS Transform custom and continuous modernization, which use agentic AI to help developers modernize code across their entire portfolio. Varun focuses on helping customers modernize their applications faster and keep them current over time.