Dual jar/war build for Spring Boot

The great thing about Spring Boot is no need for an external servlet container. All that is needed reside inside a single runnable JAR file. In a very few steps, development of a new application can be started without installation or configuration of any additional software. Yet, sometimes you might want to deploy your application to some server as a regular WAR file. For instance, you convert an existing application and want to keep your continuous delivery pipe untouched or a particular container is enforced by a company’s policy. The reason for building a WAR file may vary across teams, but for development purpose a simple executable JAR file with an embedded server might be preferable.

Advertisement

Executable WAR

Before you even consider dual compilation to achieve executable JAR and deployable WAR you should be aware of another option. In this case, Spring Boot allows you to have your cake and eat it too. You can accomplish a similar result with one build in three easy steps.

  1. In your pom.xml change the packaging of the project to war.
    <packaging>war</packaging>
    
  2. In the same file mark the spring-boot-starter-tomcat dependency as provided.
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    
  3. The last step is to extend the SpringBootServletInitializer abstract class and implement its required method to point out your application runner class. You can use your runner class to extend the initializer, but it’s not mandatory and a separate class may also be used. Here is the example with the runner:
    @SpringBootApplication
    public class ApplicationRunner extends SpringBootServletInitializer {
    
        public static void main(String[] args) {
            SpringApplication.run(ApplicationRunner.class, args);
        }
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
            return builder.sources(ApplicationRunner.class);
        }
    
    }
    

Now, if you run the Maven build your application will be built as a WAR file, which can be easily deployed on any servlet container. Since you marked the embedded Tomcat dependency as provided you probably expect it isn’t packed inside the archive, but the reality is different. All dependencies related to Tomcat are placed in a WEB-INF/lib-provided directory inside the WAR. Thanks to that, these jars aren’t added to the classpath when the application is deployed in a container, but since they are packed inside, Spring Boot can use them to run the application just like a regular JAR file.

Removing lib-provided directory

But what if you don’t want the extra Tomcat dependencies in your WAR file to make it less heavy? Actually, these libraries are required only for the development phase. To achieve such a goal you can create two separate build profiles. The first one will create a simple executable JAR file, and the second will be responsible for packaging the application into a releasable WAR file.

  1. First things first, you need to extend the SpringBootServletInitializer just like it is described in the third step of the executable WAR guide in the previous paragraph.
  2. Second, in your pom.xml change set the packaging with a property placeholder that you will declare in your profiles.
    <packaging>${project.packaging}</packaging>
    
  3. Next, in the same file, create the development Maven profile. This is the one that you will use most of the time, so it is advised to make it active by default to facilitate your work. In addition, declare the aforementioned property with the value set to jar.
    <profiles>
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <project.packaging>jar</project.packaging>
            </properties>
        </profile>
    </profiles>
    
  4. The last thing that you need is the profile for the WAR file. If you’d completed the whole Executable WAR guide before this one, at this stage you should entirely remove the previously declared spring-boot-starter-tomcat dependency. Then, create another profile with the following sample:
    <profile>
        <id>release</id>
        <properties>
            <project.packaging>war</project.packaging>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <exclusions>
                    <exclusion>
                        <artifactId>spring-boot-starter-tomcat</artifactId>
                        <groupId>org.springframework.boot</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    </profile>
    

    What is actually going on here? The profile declares the packaging property as war. Next, the Tomcat dependencies are marked as excluded, this prevents the build from creating the lib-provided directory. However, without these dependencies your project won’t compile. That is why you have to declare another dependency on the servlet API specification. Since your servlet container that is going to be used for deployment definitely has its own copy of the jar you should mark the library as provided.

That is all. By default, your build runs with the dev profile without any additional configuration. If you want to create the release package, just activate the second profile that produces the non-executable WAR file. In case of any problem with the configuration, the whole pom.xml is available here.

The choice is yours

Spring Boot allows us to create a deployable WAR file with two possible options. Single build for an executable WAR file is mentioned in the official documentation, which suggests it as a preferable method for the Spring Boot team to achieve the goal. If you’re ok with a few additional megabytes of libraries, that’s definitely the way to go. Especially when you follow the motto “Build once, deploy everywhere”. With the dual build approach a release package is lighter, but in exchange for its ability to execute. Nothing comes for free, yet for some teams a smaller deploy unit may be important. There is no better option, just make the decision based on your real needs.

Facebooktwittergoogle_plusredditlinkedinmail
Advertisement