Fitting the artifact size in Spring Boot applications
By: Martín Lamas
Tags: Deploy, Spring, Java, Spring Boot

Photo by Calum MacAulay on Unspash

A few weeks ago, I started working in a new Java project with Spring Boot as the base framework. Despite the advantages of using this framework, we found an inconvenience in the size of the artifact generated in the build process. In this post I will explain the approach that we used to solve this problem.

The build process of a Spring Boot app

Spring Boot is a great framework that eases the bootstrapping and development of our new Spring projects. To achieve this, the framework provides a set of starters that allow us to avoid the inclusion of multiple dependencies and to establish a default configuration to quickly start coding.

Using the Spring initializr website, we can generate a skeleton to start coding our new Spring Boot project. It provides a pom.xml configuration file with some defaults including the starters that we have selected. The spring-boot-maven-plugin is included in the build section to generate an artifact and make the application executable. This generated artifact contains the following things:

The application classes and the associated resources

The dependencies

Some Spring classes that give support to deploy the application in a Tomcat Server or to run it standalone

Unfortunately this impacts the size of the generated artifact: around 40–50 MB even in a small web application. In our case, this is a problem because we need to make regular deploys through a slow network.

Using the spring-boot-thin-launcher plugin we found a solution to this problem. It extracts the application dependencies from the artifact. With this approach, we reduced the artifact size speeding up the deploy process.

How does the spring-boot-thin-launcher works?

The idea is simple: the plugin excludes the dependencies at the build process and adds the ThinJarWrapper as the main class in the application manifest.

When the application starts, the ThinJarWrapper class automatically retrieves the necessary dependencies to start the application. It searches for them in the pom.xml and thin.properties files. The thin.properties file is optional, and we may use it to include some additional dependencies. Then, these dependencies are loaded in the classpath and, finally, the original main class is executed.

By default, the application dependencies are cached in the local maven repository. Anyway, we can change this behaviour and force ThinJarWrapper to store them in another place. If so, we must use the -Dthin.root parameter when we start the application.

Configuring the spring-boot-thin-launcher

Setting up the plugin is easy. All we have to do is add the plugin as dependency of the spring-boot-maven-plugin :

After doing a mvn package , the project artifact jar file should be generated in the target directory. Now, the file size should be around a few KB.

Speeding up the deploy process

As I said before, in our particular case, the server that hosts the applications is placed in a slow external network. To minimize the deploy time we are going to transfer the minimum required artifacts on every deploy. The first step is to add another plugin that places all the generated artifacts in the target/thin/root folder, instead of putting them in the maven cache. This folder will contain the application artifact and its dependencies. To enable this plugin we need to put the following code into the build section of the pom.xml file:

In combination with the plugin, we use the following bash script:

The trick here is to use rsync to synchronize the binaries. The first time we deploy the application all binaries will be synchronized (including the application artifact and its dependencies). When we deploy a new release only the artifact and the new dependencies will be synchronized :)

Now we can run the application in the server using the -Dthin.root option to load the dependencies from the /apps/ourawesomeapp/usr/bin folder.

Summing up

With the spring-boot-thin-launcher plugin we can reduce the artifact size of our applications extracting the dependencies from them. This can be useful in scenarios like this. Also, it’s possible to deploy multiple related applications (a set of microservices, for example) sharing the dependencies among them.