AdBlock Detected

It looks like you're using an ad-blocker!

Our team work realy hard to produce quality content on this website and we noticed you have ad-blocking enabled. Advertisements and advertising enable us to continue working and provide high-quality content.

GraalVM Native Images with Spring Native

In this article, we will provide an introduction to GraalVM native images with Spring Native. This is a significant improvement within the Spring framework that allows us to optimize and enhance the performance during the startup of our applications.

Spring Native, currently in beta release (Currently you can use without beta release), enables us to convert our Spring projects into executable images known as native images. This leads to a substantial improvement in startup times and reduced runtime memory overhead when compared to traditional JVM-based applications.

To harness these improvements, Spring Native relies on GraalVM, a Java Virtual Machine (JVM) and Java Development Kit (JDK) based on HotSpot/OpenJDK. This enhancement has been eagerly awaited, especially in the context of cloud-native applications, where fast startup and scalability are crucial. Furthermore, frameworks like Quarkus, which operates on GraalVM and HotSpot, align perfectly with cloud-native goals.

The initial version of Spring Native was released on March 11, 2022 and is available on start.spring.io.

The current version of Spring Boot allow work with Spring Native, I wrote this article before the Version 3 of Spring Boot.

Key Features of GraalVM

GraalVM provides us with many different features, among which it offers a component known as Substrate VM. This component allows us to compile bytecode with AOT (Java Ahead of Time) into a native executable.

This enhancement will significantly improve the performance of any Spring Boot application. During runtime, it performs various operations, such as reading files from the classpath and conducting numerous reflective operations.

To address the limitations mentioned above, the approach taken is to record all interactions with the application running in a JVM. This is accomplished through a Java agent provided by Graal VM. Once the execution is completed, all interactions are downloaded into dedicated configuration files.

The common way to deal with this limitation is by recording all interactions with the application running in a JVM using a Java agent provided by Graal VM. At the end of execution, the agent downloads all recorded interactions into dedicated configuration files.

Example with Spring Native and Database

Next, we’re going to dive into Spring Native by exploring a small example to uncover the potential that this new module can offer.

This example will be created using Cloud Native Buildpacks, allowing us to generate Docker-compatible images that can be executed anywhere. Spring Boot, through Maven, provides us with this functionality.

If you’ve had the chance to work with an example or use Quarkus, you may have noticed its fast startup times and minimal memory consumption, even for regular applications. This module’s goal is precisely to reduce startup times and memory usage. However, the best way to appreciate it is by witnessing it in action.

The application we’ve used for this example is developed with Java 11, following a fully reactive approach, and utilizes an in-memory database like H2. Let’s delve into the various components of this application:

Spring Native Dependencies in Maven

The first thing we’re going to do is add the necessary Maven dependencies to incorporate this Spring module. On one hand, we’ll include the spring-aot-maven-plugin plugin, and on the other hand, we’ll add the spring-native dependency.

 <plugin>
        <artifactId>spring-aot-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>generate</goal>
            </goals>
            <id>generate</id>
          </execution>
        </executions>
        <groupId>org.springframework.experimental</groupId>
        <version>0.9.1</version>
     </plugin>
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <image>
            <builder>paketobuildpacks/builder:tiny</builder>
            <env>
                <BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
            </env>
        </image>
    </configuration>
</plugin>
    <dependency>
      <artifactId>spring-native</artifactId>
      <groupId>org.springframework.experimental</groupId>
      <version>0.9.1</version>
    </dependency>

Spring Native 0.9.1 only supports Spring Boot Starter Parent 2.4.4.




Create reactive application

As we mentioned earlier, the application developed to test Spring Native is entirely reactive. Therefore, we have utilized Spring’s WebFlux and Spring Data Reactive. Let’s delve a bit deeper into the implementation:

Our API will be fully reactive, using WebFlux. On one hand, we create the routes and the handler responsible for these routes; on the other hand, we will make use of the spring-data-r2dbc library to ensure our database connection is also reactive:

@Configuration
public class CarRoutes {


  @Bean
  public RouterFunction<ServerResponse> routes(CarHandler handler) {
    return route().path(
        "/car", builder -> builder
            .GET("/", handler::getAll)
            .GET("/{id}", handler::getOne)
    ).build();
  }
}




@RequiredArgsConstructor
public class CarHandler {

  private final CarService service;

  public Mono<ServerResponse> getAll(ServerRequest req) {
    var all = service.findAll(Sort.by("model", "brand"));
    return ok().body(BodyInserters.fromPublisher(all, Car.class));
  }

  public Mono<ServerResponse> getOne(ServerRequest req) {
    var mono = service
        .findById(Long.valueOf(req.pathVariable("id")))
        .switchIfEmpty(Mono.error(() -> new ResponseStatusException(NOT_FOUND)));
    return ok().body(fromPublisher(mono, Car.class));
  }
}

In the two previous classes, we have created the endpoints on one hand and a handler on the other hand, which is responsible for performing the logic of the endpoints.

Next, we create the repository to make it entirely reactive, using ReactiveSortingRepository.

public interface CarRepository extends ReactiveSortingRepository<Car, Long> {

}

Once we have created and verified the correct functioning of the application, it’s time to generate the image:

mvn spring-boot:build-image

The AOT compilation takes some time, and once it’s done, we can run the image as follows:

docker run -it --rm -p8080:8080 docker.io/library/spring-native-example:1.0-SNAPSHOT     

When trying to start the application, we will encounter a series of issues:

  • ConnectionFactoryConfigurations: It will give an error stating that it cannot configure the connectionFactory. It appears that Spring Native has not included some autoconfiguration, and we will add it via resources in the META-INF:
[
{
  "name":"org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryConfigurations$PooledConnectionFactoryCondition",
  "methods":[{"name":"<init>","parameterTypes":[] }]
},
{
  "name":"java.time.LocalDate"
},
{
  "name":"java.lang.Iterable",
  "allDeclaredFields":true,
  "allDeclaredMethods":true,
  "allPublicMethods":true
},
{
  "name":"com.refactorizando.example.springnative.Person",
  "allDeclaredFields":true,
  "allDeclaredMethods":true,
  "allPublicMethods":true,
  "allDeclaredConstructors":true
},
{
  "name":"org.springframework.data.domain.Sort"
}
]
  • Proxies: It will throw some proxy errors because when using Spring Data, it automatically creates a proxy for the repository interface. However, with GraalVM, you need to configure it as follows in the META-INF:
[
  ["com.refactorizando.example.springnative.CarRepository","org.springframework.data.repository.Repository","org.springframework.transaction.interceptor.TransactionalProxy","org.springframework.aop.framework.Advised","org.springframework.core.DecoratingProxy"]
]
  • Serialization: With GraalVM, we also need to add serialization configuration in the META-INF for our Car class:
[
{
  "name":"com.refactorizando.example.springnative.Car"
},
{
  "name":"java.time.LocalDate"
},
{
  "name":"java.lang.String"
},
{
  "name":"java.time.Ser"
}
]

Once we’ve finished with the configurations, we reconfigure the image as previously indicated and start it with Docker. We can test it with the following endpoint and see that it returns information:

curl http://localhost:8081/car/1

Conclusion

Although Spring Native is not yet in its final version (the current version supports it), and as we’ve seen, some manual configurations are required, we can already start testing its capabilities and the functionalities it offers. Moreover, it is undoubtedly one of the most anticipated improvements in Spring in recent times.

In this post about GraalVM native images with Spring Native, we’ve seen how Spring has simplified the configurations for creating native images in an easy and straightforward manner.

If you’d like, you can take a look at the example created with Spring Native and database connection on our GitHub.

Leave a Reply

Your email address will not be published. Required fields are marked *