Blogs

Spring Native: Going Native with JHipster and Spring Boot

Category
Software development
Spring Native: Going Native with JHipster and Spring Boot

How we adjusted a JHipster-generated Spring Boot/Hibernate JPA application for native image build with Spring Native?

For quite some time now, (to be more precise since its announcement in March 2021), we have been closely following the progress of the Spring Native project. Ever since, the idea of Spring Boot application built as native image, with lighting-speed boot time, measured in milliseconds instead of dozens of seconds, and smaller memory footprint captured our imagination.

Then in November 2021 when Matt Raible and Josh Long posted that they had managed to build a native image of JHipster application using Spring Native I knew I had to find time and try it for myself. In this blog post I will share our (mine and colleague of mine Darko Špoljarić) experience in adjusting a JHipster generated Spring Boot/Hibernate JPA application for native image build with Spring Native.

For the purposes of this exercise we will use Spring Native engine version 0.11.1 and a JHipster (v7.5.0) generated demo application named “Geodata” (showcase of the app is available at the link: https://workshop.ag04.io/geodata/). Geodata is a simple application with two entities: Country and Currency, consisting of Spring Boot backend Rest API and Angular frontend application with JWT as authentication method.

Code repositories of both applications are available as public repositories at GitHub:

Known Issues

Before proceeding any further it would be opportune to first point out the features of the JHipster application that we were not able to make work in the native image build of Geodata application.

1. Cache

In this run, we haven’t managed to figure out how to make ehcache work with Spring Native build. There is an ongoing thread on spring-experimental project forum on this topic, and it seems that at the moment neither ehcache nor caffein are supported.

2. EntityGraph

For some reasons EntityGraph annotation on User entity is not working.

3. Logs endpoint (/management/loggers)

This endpoint returns HTML instead of JSON?

4. Configuration endpoint (/management/configprops) 

Calls to this endpoint result in the following error:

org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class org.springframework.boot.actuate.context.properties.ConfigurationPropertiesReportEndpoint$ApplicationConfigurationProperties] with preset Content-Type ‘null’)

5. Mockito is not supported

Mockito is not supported (there is a blocked issue with more info concerning this topic: https://github.com/spring-projects-experimental/spring-native/issues/1063) so all mockito tests in the project can not be run.

Building native image of Geodata Rest API

To be able to follow this blog post hands on, you need to have the following tools installed on your workstation:

  • Git client
  • Java SDK 11
  • Docker client
  • IDE of your choice (IntelliJ, VS Code, Eclipse …)

Start by cloning geodata-rest-sb repository:

git clone git@github.com:dmadunic/geodata-sb-rest.git

Now cd to the geodata-rest-sb folder and build the classic docker app image:

cd geodata-rest-sb/
./gradlew bootJar -Pprod jibDockerBuild -PimageVersion=latest

This will build “ag04/geodata-rest-sb:latest” docker image in your local docker repository.

domagoj@dm-tuxXP14G14:~$ docker images
REPOSITORY                                 TAG             IMAGE ID       CREATED         SIZE
ag04/geodata-rest-sb                       latest          b14bb3a6a517   17 seconds ago  339MB

Application can now be run with the use of the prepared docker-compose yaml file:

docker-compose -f src/main/docker/app.yml up

This will download all necessary images and once this is done start up all the services: Geodata-rest API, nginx server with Geodata Angular frontend and PostgreSQL database (available on localhost port 5433). Startup time of the geodata rest API varies and depends on the configuration of your workstation, on mine it usually takes around 15 seconds.

Note down this value we will compare it later with the startup time of the native image rest api build. If you point your browser to: “http://localhost:9000/” you should be greeted with the GeodataApp welcome screen. You can sign in using default jhipster username and credentials of user/user and admin/admin.

To shutdown geodata services execute the following command:

docker-compose -f src/main/docker/app.yml down

Finally, import the Geodata Rest API to your favorite IDE (Idea, IntelliJ,…) and we can start adjusting the app for native build. In the following sections we will provide detailed instructions on steps we have undertaken to achieve the build of the working native image of Geodata Rest API application.

Configuring gradle build configuration files

To build native image of the Spring Boot app we need Spring AOT Engine, and since geodata-rest-sb is an application built with gradle therefore the first thing we need to do is to register Spring AOT gradle plugin. Thus, we need to change setting.gradle to look as shown below:

pluginManagement {
    repositories {
       //maven { url 'https://repo.spring.io/milestone' }
       gradlePluginPortal()
       maven { url 'https://repo.spring.io/release' }
   }
   plugins {
       id 'org.springframework.boot' version "${springBootVersion}"
       id 'com.google.cloud.tools.jib' version "${jibPluginVersion}"
       id 'com.gorylenko.gradle-git-properties' version "${gitPropertiesPluginVersion}"
       id 'org.liquibase.gradle' version "${liquibasePluginVersion}"
       id 'org.sonarqube' version "${sonarqubePluginVersion}"
       id "io.spring.nohttp" version "${noHttpCheckstyleVersion}"
       id 'com.github.andygoossens.gradle-modernizer-plugin' version "${modernizerPluginVersion}"
       id 'org.springframework.experimental.aot' version "${springAotPlugin}"
   }
}
rootProject.name = "geodata-rest-sb"

What we have done is: 1) commented standard spring milestone repository and instead added spring release repository, and 2) registered spring aot plugin.

The next step is to modify the gradle.properties file. Geodata Rest API is an application generated with jhipster version 7.5.0 and is thus built on Spring Boot version 2.5.8. However, the latest version of spring AOT engine requires Spring Boot 2.6.2, thus we need to adjust some dependency versions, namely increase versions of Spring Boot to 2.6.2 and Hibernate to 5.6.3.Final. Change the “Dependency versions” section of the file to match the following snipper:

# Dependency versions
jhipsterDependenciesVersion=7.5.0
# The spring-boot version should match the one managed by
# https://mvnrepository.com/artifact/tech.jhipster/jhipster-dependencies/7.5.0
springBootVersion=2.6.2
#springBootVersion=2.5.8
# The hibernate version should match the one managed by
# https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-dependencies/2.5.8 -->
hibernateVersion=5.6.3.Final
#hibernateVersion=5.4.33
…

Additionally, at the end of the “gradle plugin versions” section add the version for spring AOT gradle plugin:

# gradle plugin version
jibPluginVersion=3.1.4
gitPropertiesPluginVersion=2.3.2
liquibasePluginVersion=2.1.1
sonarqubePluginVersion=3.3
noHttpCheckstyleVersion=0.0.10
checkstyleVersion=9.2
modernizerPluginVersion=1.6.1
springAotPlugin=0.11.1

With these changes in place, we can now switch to modifying build.gradle. The first thing we need to do is to modify buildscript section and replace milestone repository with the release one. Second, in the buildscript.dependencies section we need to register a hibernate-gradle-plugin which is needed for bytecode build time enhancement (to allow for features such as Lazy loading). Finally add the spring aot gradle plugin to the list of registered plugins. The next code snippet displays all the changes done:

buildscript {
   repositories {
       gradlePluginPortal()
       maven { url 'https://repo.spring.io/release' }
   }
   dependencies {
       //jhipster-needle-gradle-buildscript-dependency - JHipster will add additional gradle build script plugins here
       classpath "org.hibernate:hibernate-gradle-plugin:5.6.3.Final"
   }
}
 
plugins {
   id "java"
   ...
   //jhipster-needle-gradle-plugins - JHipster will add additional gradle plugins here
   id "org.springframework.experimental.aot"
}
apply plugin: 'org.hibernate.orm'

For hibernate enhancement plugin to be active, add the following block of configuration just above defaultTask declaration.

hibernate {
   enhance {
       enableLazyInitialization= true
       enableDirtyTracking = true
       enableAssociationManagement = true
       enableExtendedEnhancement = false
   }
}
defaultTasks "bootRun"

Next change we need to do is to comment springBoot section and specify some native build parameters, as shown here:

/*
springBoot {
    mainClassName = "com.ag04.geodata.GeodataRestSbApp"
}
*/
 
bootBuildImage {
    imageName = "ag04/geodata-rest-sb-native"
    builder = "paketobuildpacks/builder:tiny"
    environment = [
        "BP_NATIVE_IMAGE" : "true"
    ]
}

In this configuration block, we specify Paketo Buildpacks as target builder of our native image and a custom image name of “ag04/geodata-rest-sb-native” as our build target.

Also, in the repositories section, we need to change spring milestone repository with the release one, as is shown below:

repositories {
   // Local maven repository is required for libraries built locally with maven like development jhipster-bom.
   // mavenLocal()
   mavenCentral()
   //jhipster-needle-gradle-repositories - JHipster will add additional repositories
   maven { url 'https://repo.spring.io/release' }
}

At the moment spring devtools are not supported in native builds so we need to disable them. Change the configuration section of the build.gradle to look as follows:

configurations {
   providedRuntime
   //implementation.exclude module: "spring-boot-starter-tomcat"
   all {
       exclude group:"org.springframework.boot", module: "spring-boot-devtools"
       resolutionStrategy {
           // Inherited version from Spring Boot can't be used because of regressions:
           // To be removed as soon as spring-boot use the same version
           force 'org.liquibase:liquibase-core:4.6.1'
       }
   }
}

In this section we have also commented out the “spring-boot-starter-tomcat” exclusion, since we will need this dependency. 

With this done we have finished changing projects’ configuration files, and we can proceed to the next step, that is configuring correct project dependencies.

Configure project dependencies

Since we are using gradle, we do not need to add the spring-native dependency manually,  the Spring AOT plugin will add it automatically for us. However, we still need to do a considerable number of changes in the dependencies section of build configuration.

  1. Set explicit version for all spring dependencies

In order to avoid collision with the jhipster specified dependencies, we need to manually specify versions for ALL of the spring libraries we are using in the project. As is, for example, shown here in the the following configuration block:

implementation "org.springframework.boot:spring-boot-loader-tools:${springBootVersion}"
implementation "org.springframework.boot:spring-boot-starter-mail:${springBootVersion}"
implementation "org.springframework.boot:spring-boot-starter-logging:${springBootVersion}"
implementation "org.springframework.boot:spring-boot-starter-actuator:${springBootVersion}"
implementation "org.springframework.boot:spring-boot-starter-data-jpa:${springBootVersion}"
  1. Explicitly add dependency for spring-boot-starter-validation
implementation "org.springframework.boot:spring-boot-starter-validation:${springBootVersion}"

If we do not add this, the version of the library specified by jhipster BOM would be used, and not the one we specified in gradle.properties.

  1. Explicitly set hibernate version

Similar as in the case of the spring dependencies we need to manually set versions for the following hibernate dependencies:

implementation "org.hibernate:hibernate-core:${hibernateVersion}"
implementation "org.hibernate:hibernate-entitymanager:${hibernateVersion}"
liquibaseRuntime "org.liquibase.ext:liquibase-hibernate5:${liquibaseHibernate5Version}"
  1. Replace undertow with tomcat

Locate the following block of configuration in the build.gradle:

implementation ("org.springframework.boot:spring-boot-starter-web") {
   exclude module: "spring-boot-starter-tomcat"
}
implementation "org.springframework.boot:spring-boot-starter-undertow"

and change it to look as follows:

/*
implementation ("org.springframework.boot:spring-boot-starter-web") {
   exclude module: "spring-boot-starter-tomcat"
}
implementation "org.springframework.boot:spring-boot-starter-undertow"
*/
implementation "org.springframework.boot:spring-boot-starter-web:${springBootVersion}"
implementation "org.springframework.boot:spring-boot-starter-tomcat:${springBootVersion}"
  1. Replace springdoc-openapi-webmvc-core with springdoc-openapi-native
//implementation ("org.springdoc:springdoc-openapi-webmvc-core")
implementation ("org.springdoc:springdoc-openapi-native:1.6.0")
  1. Disable spring-cloud-starter-bootstrap
//implementation "org.springframework.cloud:spring-cloud-starter-bootstrap"
  1. Find the following block of configuration:
if (!project.hasProperty("gae")) {
    runtimeOnly "io.jsonwebtoken:jjwt-impl"
    runtimeOnly "io.jsonwebtoken:jjwt-jackson"
} else {
   implementation "io.jsonwebtoken:jjwt-impl"
   implementation "io.jsonwebtoken:jjwt-jackson"
}

And modify it to look as follows:

implementation "io.jsonwebtoken:jjwt-impl"
runtimeOnly "io.jsonwebtoken:jjwt-jackson"
  1. Remove spring-boot-devtools dependency
//developmentOnly "org.springframework.boot:spring-boot-devtools:${springBootVersion}"
  1. Remove cache dependencies

Since we haven’t been able to make cache to work (bootstrap code generated by AOT is incompatible with the method signatures of the org.springframework.cache.jcache.config.ProxyJCacheConfiguration.java), go ahead and simply comment the following libraries:

//implementation "org.springframework.boot:spring-boot-starter-cache"
//implementation "javax.cache:cache-api"
//implementation "org.ehcache:ehcache"
//implementation "org.hibernate:hibernate-jcache"

Since no cache libraries are available on the application classpath, we also need to disable hibernate second level cache. This is done by setting the following property to false inside application.yml file:

hibernate.cache.use_second_level_cache: false

Fix logging

Based on this discussion from spring native forum, it seems that support for Logback logging library will be available with 0.11.3 version of Spring AOT engine, so for now just delete logback-spring.xml file from the src/main/resources folder.Also I would suggest to reduce the level of output to logs by setting logging properties in application-prod.yml on these lines:

logging:
 level:
   ROOT: INFO
   tech.jhipster: INFO
   com.ag04.geodata: DEBUG
   io.netty: ERROR
   org.springframework: INFO

Next step is to apply several code changes that are necessary for native image of the application to work.

Java code adjustments

The first thing that we need to do is to add necessary hints to main Spring boot class: com.ag04.geodata.GeodataRestSbApp.java

GraalVM relies on these hints for building native image, this is the source of information for the engine concerning how to add classes that are not explicitly used in the code to target build. 

Without these hints, for example, Liquibase, Jwt authentication, async processing or thymeleaf engine would not work.

So, go ahead and ad this block of annotations to the main spring boot class:

@TypeHint(
   types = {
       org.HdrHistogram.Histogram.class,
       org.HdrHistogram.ConcurrentHistogram.class,
       liquibase.configuration.LiquibaseConfiguration.class,
       com.zaxxer.hikari.HikariDataSource.class,
       liquibase.change.core.LoadDataColumnConfig.class,
       tech.jhipster.domain.util.FixedPostgreSQL10Dialect.class,
       org.hibernate.type.TextType.class,
       io.jsonwebtoken.impl.DefaultJwtParserBuilder.class,
       io.jsonwebtoken.impl.DefaultJwtBuilder.class,
       java.util.Locale.class,
       com.sun.mail.smtp.SMTPSSLTransport.class,
   }
)
@JdkProxyHint(
   types = {
       org.springframework.data.jpa.repository.support.CrudMethodMetadata.class,
       org.springframework.aop.SpringProxy.class,
       org.springframework.aop.framework.Advised.class,
       org.springframework.core.DecoratingProxy.class,
   }
)
@TypeHint(types = { java.util.Locale.class }, methods = { @MethodHint(name = "getLanguage") })
@SpringBootApplication
@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class })
public class GeodataRestSbApp {
…
}

The next step is to fix Task Executor configuration. Without this fix, the application behaves as if the Spring didn’t invoke the afterPropertiesSet method on the ExceptionHandlingAsyncTaskExecutor – which it does if you enable trace and debug logs in the relevant classes and check the output. Anyhow, the problem is solved by invoking the method manually when creating the bean in the AsyncConfiguration class.

To do so apply the changes from the following snippet to the AsyncConfiguration:

@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
   ...
   //return new ExceptionHandlingAsyncTaskExecutor(executor);
   ExceptionHandlingAsyncTaskExecutor taskExecutor = new ExceptionHandlingAsyncTaskExecutor(executor);
 
   try {
       taskExecutor.afterPropertiesSet();
       return taskExecutor;
   } catch (Exception e) {
       throw new RuntimeException(e);
   }
}

As noted previously, @EntityGraph annotation is not working in the current settings, so we need to comment this annotation on following methods in the UserRepository.java:

//@EntityGraph(attributePaths = "authorities")
@Cacheable(cacheNames = USERS_BY_LOGIN_CACHE)
Optional<User> findOneWithAuthoritiesByLogin(String login);
 
//@EntityGraph(attributePaths = "authorities")
@Cacheable(cacheNames = USERS_BY_EMAIL_CACHE)
Optional<User> findOneWithAuthoritiesByEmailIgnoreCase(String email);

Also, we need to adjust the authorities property of the User entity (com.ag04.geodata.domain.User.java) to include FetchType.EAGER instruction to JPA (or the DomainUserDetailsService will fail attempting to fetch user during the login), as is shown here:

@JsonIgnore
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "jhi_user_authority",
      joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") },
      inverseJoinColumns = { @JoinColumn(name = "authority_name", referencedColumnName = "name") }
   )
   @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
   @BatchSize(size = 20)
   private Set<Authority> authorities = new HashSet<>();

Fixing support for mail sending required some hacking and workarounds. For mysterious reasons, when running inside native build, spring autoconfiguration for MailSender does not kick in and MailSender is not configured. After some pondering over this issue, we decided to manually configure the Java mail sender, in the same way Spring itself configures JavaMailSender.

Therefore, inside the package com.ag04.geodata.config we added a file MailPropertiesConfiguration.java

package com.ag04.geodata.config;
 
import org.springframework.boot.autoconfigure.mail.MailProperties;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class MailPropertiesConfiguration extends MailProperties {
}

Now, in the same package also create file MailConfiguration.java with the following content:

@Configuration
public class MailConfiguration {
  
   @Bean
   JavaMailSenderImpl mailSender(MailProperties properties) {
       JavaMailSenderImpl sender = new JavaMailSenderImpl();
       applyProperties(properties, sender);
       return sender;
   }
 
   private void applyProperties(MailProperties properties, JavaMailSenderImpl sender) {
       sender.setHost(properties.getHost());
       if (properties.getPort() != null) {
           sender.setPort(properties.getPort());
       }
       sender.setUsername(properties.getUsername());
       sender.setPassword(properties.getPassword());
       sender.setProtocol(properties.getProtocol());
       if (properties.getDefaultEncoding() != null) {
           sender.setDefaultEncoding(properties.getDefaultEncoding().name());
       }
       if (!properties.getProperties().isEmpty()) {
           sender.setJavaMailProperties(asProperties(properties.getProperties()));
       }
   }
 
   private Properties asProperties(Map<String, String> source) {
       Properties properties = new Properties();
       properties.putAll(source);
       return properties;
   }
}

The above source is basically taken from the spring package. With these last changes in place we are almost done with the adjusting goedata-rest-sb application for native build, and there is one more piece of configuration we need to add.

We also need to clean up a UserService.java, namely to remove all references to the CacheManager class from it (since we do not have a working cache provider at the moment). What needs to be done is to comment private property CacheManager, remove CacheManager from the signature of the constructor (as shown below):

  //private final CacheManager cacheManager;
  
   public UserService(
       UserRepository userRepository,
       AuthorityRepository authorityRepository,
       PasswordEncoder passwordEncoder
   ) {
       this.userRepository = userRepository;
       this.authorityRepository = authorityRepository;
       this.passwordEncoder = passwordEncoder;
   }

and also comment the entire body of the clearUsercaches() method:

private void clearUserCaches(User user) {
       /*  Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE)).evict(user.getLogin());
       if (user.getEmail() != null) {  Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE)).evict(user.getEmail());
       }*/
}

Finally to avoid compilation errors delete the entire class file: com.ag04.geodata.config.CacheConfiguration.java from the project.

Configuring static resources

The last piece of the puzzle concerns the access to static resources in the native image. Official GraalVM documentation states the following:

By default, the native image builder will not integrate any of the resources which are on the classpath during the generation into the final image.

As a result of this, any attempt to access static files located inside jar files available on the classpath will fail! In the case of our application this means that in the native image, liquibase will not be working, since it needs access to the XSD files, or that JavaMailSender will not be able to be initialized because it will fail to access several files from jakarta-mail.jar.

To overcome this we need to instruct GraalVM to include these files in our build. We can do that by defining which static files to be included (or excluded) in the file named: resource-config.json, located in the folder src/main/resources/META-INF/native-image/. GraalVM will automatically detect any such files and apply them. Further customization is also available with the spring-native hints as documented here.

Therefore go ahead and create this file with the following content:

{
   "resources": {
       "includes": [
           {"pattern" : "\\Qorg/springframework/mail/javamail/mime.types\\E"},
           {"pattern" : ".*/javamail.default.address.map"},
           {"pattern" : ".*/javamail.default.providers"},
           {"pattern" : ".*/javamail.charset.map"}
       ]
   }
}

This solves all the Java Mail Sender initialization problems, what remains to be tackled is the problem of including all of liquibase XSD files. Fortunately, there is a Pull Request that provides solutions to this problem. Take the two files from this PR (resource-config.json and reflect-config.json) and save them to “src/main/resources/META-INF/native-image/liquibase/” folder.

Finally, it is time to build native image of the Geodata Rest API.

Building and running native image

To build native image issue the following command inside root project folder:

./gradlew clean bootBuildImage

This will start a build, be patient since building native images takes considerably longer time than building a normal one.

Once the build is done, you can check if image is available in you local docker registry:

docker images

And in the list of the local container images you should now also see “ag04/geodata-rest-sb-native” image:

REPOSITORY                                 TAG             IMAGE ID       CREATED         SIZE
...
ag04/geodata-rest-sb-native                latest          aeb33a851c36   42 years ago    201MB
...

As can be seen, this image is noticeably smaller than the standard one (210Mb vs 313Mb).

In order to use this image open docker-compose file appl.yml we used previously at the beginning of this exercise, and modify geodata-rest-sb service image “name” property to refer to the new native image, as shown here:

geodata-rest-sb:
   image: ag04/geodata-rest-sb-native:latest
   environment:
     - _JAVA_OPTIONS=-Xmx512m -Xms256m

Save the changes and run the application by executing:

docker-compose -f src/main/docker/app.yml up

If all went well you should see the output similar to the one bellow:

geodata-rest-sb_1           | 2022-02-02 19:02:44.576  INFO 1 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 9 endpoint(s) beneath base path '/management'
geodata-rest-sb_1           | 2022-02-02 19:02:44.578  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
geodata-rest-sb_1           | 2022-02-02 19:02:44.578  INFO 1 --- [           main] com.ag04.geodata.GeodataRestSbApp        : Started GeodataRestSbApp in 0.417 seconds (JVM running for 0.418)
geodata-rest-sb_1           | 2022-02-02 19:02:44.579  INFO 1 --- [           main] com.ag04.geodata.GeodataRestSbApp        : 
geodata-rest-sb_1           | ----------------------------------------------------------
geodata-rest-sb_1           | 	Application 'geodataRestSb' is running! Access URLs:
geodata-rest-sb_1           | 	Local: 		http://localhost:8080/
geodata-rest-sb_1           | 	External: 	http://172.25.0.4:8080/
geodata-rest-sb_1           | 	Profile(s): 	[prod, api-docs, awsses]
geodata-rest-sb_1           | ---------------------------------------------------------

In my case, the Geodata Rest API built as native image started up in just 418 ms compared to 15 seconds in the case of the image built in a standard way! Which is really impressive.

Conclusion

Having an understanding of what the native image builder does – shaves off excess classes, methods, resources etc. that it deems unreachable – the task at hand seemed simple: learn and understand ways to pass hints to the builder, then employ those ways to instruct it to leave used code and resources in the final image.

However, this turned out to be all but simple. There are several ways to pass hints, and several layers of abstraction on top of the annotations in org.springframework.nativex.hint package. The docs are so-so, and there was a lot of trial and error going on. Taking into account image build times, this effort was very time consuming.

Furthermore, many exceptions didn’t simply state “this class/method/resource is missing”. What happened instead is that the framework/library got initialized or used the wrong way because internal exceptions sometimes got caught and recovered from, or branching conditions got evaluated differently than in a non-native context. Then, 5 miles down the road something blew up giving you no obvious thing to fix – i.e. hint to employ. Being a detective in the library source and backtracking from the exception you got was a must have skill.

The final jalapeno is that certain code didn’t get invoked until you triggered a certain feature. E.g. sending a mail, doing work in a separate WorkExecutor thread etc. This implies that when you convert your app to a native variant, you’re left with a full feature sweep to do. Sometimes – many times – not a simple feat at all.

Given all that, we hold the opinion that this is still not ready for the mass market. And that’s when taking into account most of the features used here are basic and we didn’t integrate with anything external other than the DB or email server. What we would like though – and what surely is an area many library authors are working on – is to see better native support. Namely

  • Clearer error messages
  • Friendlier ways to choose and pass hints to the native image builder 

Finally – all the negatives aside – the positives are extremely convincing. Startup time is next to nothing, memory consumption is decreased, as well as the image size – and those alone are big enough reasons to seriously assess this approach.

Closing remarks

By the time we finished this article, new versions of both JHipster (v7.6.0) and Spring AOT plugin (0.11.2) came out. All with dozens of improvements: upgrade of JHipster to Spring Boot 2.6.3. (which is the target working version for Spring AOT 0.11.2 plugin) support for spring devtools, and many more.

However, as great as this sounds, it seems that in the world of native spring images nothing is simple, and in our case this proved to be more of a step back than a step forward. Upgrading to  the 0.11.2 version of the Spring AOT plugin introduced a problem of circular dependencies between liquibase bean and dataSourceScriptDatabaseInitializer bean.

A problem that does not exist outside of native build, and we were unable to either solve it or to find a suitable workaround. Moreover, Spring AOT plugin 0.11.1 we used in this example does not work properly with Spring Boot 2.6.3.This problem is deeply rooted in the Spring AOT plugin, and as it stands now we decided to leave things as they are and tackle this issue in some future exercise/blog post.

This latest experience strengthened our opinion that Spring Native is without any doubt very promising technology, and that one should keep close track of it, though its application at the moment should be limited to special cases.

Resources

Fully completed working native build version of the Geodata Rest API is available on “native” branch of the project github repository.

Official spring documentation
(https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/)

GraalVM reference manual
(https://www.graalvm.org/reference-manual/)

Matt Raible Spring Native build examples
(https://github.com/mraible/spring-native-examples)

Next

Blog

Notch Talks: A Senior’s Perspective

Agency04 Senior Engineers

Company

Our people really love it here

Evolution of expertise

The agency was founded in 2014 by seasoned industry veterans with experience from large enterprises. A group of Java engineers then evolved into a full-service company that builds complex web and mobile applications. 

Flexibility is our strong suit — both large enterprises and startups use our services. We are now widely recognized as the leading regional experts in the Java platform and Agile approach.

We make big things happen and we are proud of that.

Personal development

If you join our ranks you’ll be able hone your coding skills on relevant projects for international clients.

Our diverse client portfolio enables our engineers to work on complex long term projects like core technologies for Delivery Hero, Blockchain and NFTs for Fantasy Football or cockpit interface for giants like Strabag. 

Constant education and knowledge sharing are part of our company culture. Highly experienced mentors will help you out and provide real-time feedback and support.

Contact

We’d love to hear from you