CodeVomit

CodeVomit is code churned out thousands of lines at a time, sometimes using CopyAndPasteProgramming, but usually by just brain dumping onto the keyboard. [http://c2.com/cgi/wiki?CodeVomit]

How to provide a Spring Boot "fat JAR" with external dependencies

Published on: 26/11/2016

How to provide a Spring Boot "fat JAR" with external dependencies

Spring Boot relies on the mechanism of "Autoconfiguration": the run time support that underlies a Spring Boot application is able to create and load objects into the context based on what it finds on the classpath.

You just add a dependency in the POM and a whole range of beans is available at run time, without you having to do anything in your own code. But there's more: certain beans' implementations are chosen depending on what is available on your classpath. The cool thing is that Spring Boot allows you to plug into this mechanism and become a first class citizen of the framework.

But there's a problem: what if you want to plug new implementations without recompiling your core application? By default this is not possible because the Spring Boot Maven plugin creates a JAR with the following structure:

    <base folder>
    ├───BOOT-INF
    │   ├───classes
    │   │   ├───i18n
    │   │   ├───static
    │   │   ├───templates
    │   │   └───xyz (your java packages)
    │   └───lib
    ├───META-INF
    └───org (spring runtime support packages)

and a Manifest that looks like this:

    Manifest-Version: 1.0
    Implementation-Title: bootlog
    Implementation-Version: 0.0.1
    Archiver-Version: Plexus Archiver
    Built-By: merka
    Implementation-Vendor-Id: xyz.codevomit
    Spring-Boot-Version: 1.4.0.RELEASE
    Implementation-Vendor: CodeVomit Productions
    Main-Class: org.springframework.boot.loader.JarLauncher
    Start-Class: xyz.codevomit.bootlog.BootlogApplication
    Spring-Boot-Classes: BOOT-INF/classes/
    Spring-Boot-Lib: BOOT-INF/lib/
    Created-By: Apache Maven 3.3.3
    Build-Jdk: 1.8.0_65
    Implementation-URL: http://projects.spring.io/spring-boot/bootlog/

The important entries are Main-Class and Start-Class. The first is the actual main class called by the JVM. In this default case, the JarLoader is only able to locate and load classes inside BOOT-INT and JARs inside the lib directory. With this loader there's no way to add external JARs to the classpath.

Start-Class is, quite obviously, the entry point of your implementation.

Luckily Spring Boot has a simple way to enable the inclusion of external JARs. First of all, you'll have to recompile your project setting the ZIP layout for the Spring Boot Maven plugin. You do this with the following element in pom.xml:

        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <layout>ZIP</layout>
            </configuration>
        </plugin>

Your Manifest should look almost like the previous one, except for the Main-Class entry, which now is:

    Main-Class: org.springframework.boot.loader.PropertiesLauncher

PropertiesLauncher, unlike JarLauncher can read a property (loader.path), in which the runtime classpath is specified. With this new fat JAR you are now able to start the application with the command

    java -Dloader.path=lib,external-jar.jar -jar my-app.jar

or

    java -jar my-app.jar --loader.path=lib,external-jar.jar

the value of the loader.path property is described as follows

Comma-separated Classpath, e.g. lib,${HOME}/app/lib. Earlier entries take precedence, just like a regular -classpath on the javac command line. (documentation here)