Improve your SpringBoot app with jarmode
by Kévin Delabroye, back-end developper at Vaduo
Introduction
Are you facing a problem ? Is your SpringBoot application startup time really high ? Most of the computer information systems we know need to be fast and responsive. Together, we’ll explore a technique that could help you improve this aspect in your jar files, using the jarmode tools through the spring-boot-maven-plugin (sorry Gradle fans).
What is jarmode?
Strictly speaking, it is a Java launch parameter applicable to SpringBoot jars, particularly those that use the spring-boot-maven-plugin. It allows the use of a Spring principle called "Layers."
Layers in Spring
If you’re curious, you might have already opened the contents of a SpringBoot .jar. Historically, they were structured like this:
- The jar launch classes
- Your project dependencies (BOOT-INF/lib)
- Your project code (BOOT-INF/classes)
This structure is specific to Spring, so they had the freedom to change it and add layers. Spring offers the following four layers by default:
dependencies(for released dependencies)snapshot-dependencies(for snapshot dependencies)spring-boot-loader(for project launch)application(for your code and resources)
A layers.idx file indicates the distribution of the jar files under these layers. Here is an example:
- "dependencies":
- BOOT-INF/lib/library1.jar
- BOOT-INF/lib/library2.jar
- "spring-boot-loader":
- org/springframework/boot/loader/launch/JarLauncher.class
- ... <other classes>
- "snapshot-dependencies":
- BOOT-INF/lib/library3-SNAPSHOT.jar
- "application":
- META-INF/MANIFEST.MF
- BOOT-INF/classes/a/b/C.classThis new structure is interesting and can be used via the aforementioned jarmode parameter. Let's see how.
Project Prerequisites
Ensure you are using the spring-boot-maven-plugin to build your project. You will add the repackage goal, which will allow you to recreate your jar during the build. The plugin configuration in your pom.xml will look like this :
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>Build your project (you might want to grab a coffee as well, depending on the case). You’ll get a beautiful .jar as usual, aaaaaand….. nothing changes.
Don’t panic, this is just the first step !
Using jarmode
With simple command lines, we will extract the application layer to keep only our code at launch, with references to our dependencies only when needed.
$ java -Djarmode=tools -jar my-app.jar extract --destination applicationThis command will create an application folder where you launched it. This directory will contain a lib subdirectory and your lightweight my-app.jar. Indeed, lib contains all your project dependencies.
You will then launch your application with the following line :
$ java -jar application/my-app.jarWe made it ! Your application should now launch much faster. Of course, this is not the only possible optimization, but it’s a path that personally allowed me to go from a 2 minutes startup time (yes) to ten seconds today.
To go further…
An idea to explore would be to reuse some dependencies common to your information system by extracting them in this way. It is possible to customize Spring layers.
It is also possible to use jarmode in Docker images to make them more optimized (and maybe lighter too ?). Here is an example of a Dockerfile that uses this technique :
# Perform the extraction in a separate builder container
FROM bellsoft/liberica-openjre-debian:17-cds AS builder
WORKDIR /builder
# This points to the built jar file in the target folder
# Adjust this to 'build/libs/*.jar' if you're using Gradle
ARG JAR_FILE=target/*.jar
# Copy the jar file to the working directory and rename it to application.jar
COPY ${JAR_FILE} application.jar
# Extract the jar file using an efficient layout
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted
# This is the runtime container
FROM bellsoft/liberica-openjre-debian:17-cds
WORKDIR /application
# Copy the extracted jar contents from the builder container into the working directory in the runtime container
# Every copy step creates a new docker layer
# This allows docker to only pull the changes it really needs
COPY --from=builder /builder/extracted/dependencies/ ./
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
COPY --from=builder /builder/extracted/application/ ./
# Start the application jar - this is not the uber jar used by the builder
# This jar only contains application code and references to the extracted jar files
# This layout is efficient to start up and CDS friendly
ENTRYPOINT ["java", "-jar", "application.jar"]One could easily imagine a container dedicated to building the libraries common to your codebase, which would be loaded by the images of your web services in the same way as in the example.
