Building Spring Boot Applications with YugabyteDB: A Guide to Creating Native Images Using GraalVM

Srinivasa Vasu

In the world of microservices and serverless computing, the use of GraalVM native images is gaining a lot of attention and momentum.

Java applications can be compiled either just-in-time (JIT) or ahead-of-time (AOT). Although discussing JIT and AOT is beyond the scope of this blog, let’s quickly compare and contrast the two.

  • AOT (Ahead-of-time) compilation is a type of static compilation that generates machine code optimized for a specific microprocessor.
  • JIT (Just-in-time) compilation is the process of compiling Java bytecode to machine code at runtime.

The two strategies have their own merits and tradeoffs. In this blog, let’s focus on GraalVM’s AOT compilation for a Spring Boot application built with YugabyteDB’s JDBC driver. The ahead-of-time compiled binary output that GraalVM produces is ideal for cloud-scale applications that require quicker startup times, on-demand horizontal scalability, less memory consumption, and better performance. These benefits are significant in both technical and commercial terms.

Before You Get Started

  • Follow YugabyteDB Quickstart instructions to run a local YugabyteDB cluster. Test YugabyteDB’s YSQL API to confirm that you have YSQL service running on “localhost:5433”.
  • Gradle 7.5+
  • JDK 17+
  • GraalVM 22.3.r17+

You can use SDKMAN to install Gradle and JDK dependencies.

The source code repository includes a Gitpod launcher file so you can test everything through a browser interface in the cloud without installing these prerequisites on a local workstation.

Getting started

You can find the code snippet we’ll be using in this GitHub repository. It consists of a simple JPA-based web application with CRUD functionality. Clone this repository to a local workstation and launch your preferred IDE in the “yb-ms-data” directory to quickly navigate and explore framework-specific code.

git clone -b devx

Navigate to the springboot folder,

cd yb-ms-data/springboot

Using SDKMAN’s cli, add the GraalVM’s native-image binary to the build path. This is required to compile and produce the native image. Open a terminal window in the same project directory and run the following:

sdk use java 22.3.r19-grl

* 22.3.r19-grl is the GraalVM version. Replace it with the appropriate version as per the prerequisites.

Trigger a native build

To trigger a native build,

gradle nativeCompile

For AOT compilation, the bytecode (in the application and other dependencies that can be called at runtime) must be known at build time. This requirement makes it difficult to develop applications that use dynamic language features like reflections, proxy objects, native interfaces, etc. In Spring Boot 3.0+, the build phase has an additional AOT goal that inspects and records the runtime evaluation beforehand. The framework needs to detect and build the reachability metadata that GraalVM won’t be able to infer.

In this case, even with YugabyteDB’s JDBC driver, we would hit the following exception while running the native binary. Here, the application builds successfully but fails to start. This is caused by some dependencies lacking reachability metadata.

Caused by: java.lang.RuntimeException: java.lang.NoSuchFieldException: ADAPTIVE_FETCH
    at com.yugabyte.PGProperty.<init>( ~[yb-boot-data:42.3.5-yb-1]
    at com.yugabyte.PGProperty.<init>( ~[yb-boot-data:42.3.5-yb-1]

To get around this restriction, we can give the compiler explicit runtime hints, like the ones below:

The hint is added using the annotation @ImportRuntimeHints<with_hint_details>.


Additionally, the class that contains hint information makes it easier to create reachability metadata.

static class TodoRuntimeHints implements RuntimeHintsRegistrar {


  public void registerHints(RuntimeHints hints, ClassLoader classLoader) {

    for (Field field : PGProperty.class.getDeclaredFields()) {



      typeHint -> typeHint.withField(field.getName()));


    hints.reflection().registerTypeIfPresent(classLoader, PGobject.class.getCanonicalName(),




For full details, please refer to this source file. In comparison to the bytecode compilation, the native build uses more memory and takes longer.

Run the application

The native build requires more memory. The build could fail if the local workstation doesn’t have enough headroom. When this happens, clear out some unused resources before trying again. Alternatively, you can test everything using a cloud-based browser interface by using the Gitpod launcher file.

If the native compilation is successful, it creates the output binary file under the path <project_folder>/build/native/nativeCompile/. To run the binary file with the ysql profile,


As you can see, the start-up time had been cut from a few seconds to less than a second (0.598 s).

Try it Yourself

The gitpod configuration data is contained in .gitpod-native.yml file in the source repository. Try this example by replacing the .gitpod.yml file with that data and visiting in your browser.


This blog focused on creating a native runtime binary for Spring Boot applications with YugabyteDB using GraalVM. For more information about Spring Native, visit the Spring Native documentation site.

If you have any questions or comments, join our engaging YugabyteDB Community Slack to chat with the expanding, distributed SQL community.

Srinivasa Vasu

Related Posts

Explore Distributed SQL and YugabyteDB in Depth

Discover the future of data management.
Learn at Yugabyte University
Learn More
Browse Yugabyte Docs
Read More
Distributed SQL for Dummies
Read for Free