Configuring builds for application servers with maven profiles
Maven profiles are not the most commonly used feature and there is relatively little information on how to use them in practice. They can be used to differentiate builds of the same application; virtually every element specified in one’s pom.xml may be used conditionally based on profiles. What can you use maven profiles for in real life? Mostly they are used to configure builds for different environments (test/production), though it is argued that it shouldn’t be done. In our case, we used maven profiles to prepare builds for specific application servers.
The problem
Our case was simple: we had to prepare builds for two different application servers, a tomcat and a jetty. Spring-boot’s dependencies for those servers were incompatible with one another – deploying a build on tomcat with jetty dependencies failed, and deploying tomcat build on jetty had some logging issues. While that situation was temporary, it was a severe problem we had to solve.
The solution
We’ve used maven profiles. The dependencies sections are what’s going to be different for profiles in our case. We’ve declared two profiles which defined which of the conflicting dependencies were to be used. Our profiles section looked like this:
<profiles>
<profile>
<id>tomcat</id>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
</dependencies>
</profile>
<profile>
<id>jetty</id>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http-jetty</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</profile>
</profiles>
Secondly, we wanted to make sure that a profile was actually specified during build. We’ve decided that there’d be no default profile and the person making the build would have to make a conscious decision which profile to use. That part required a little bit more work and several attempts to make work. The maven-enforcer-plugin was used to achieve our goal.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<executions>
<execution>
<id>enforce-one-profile-is-activated</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireActiveProfile>
<message>Choose jetty or tomcat profile by specifying "-P *profile*" after mvn goals</message>
<profiles>jetty,tomcat</profiles>
<all>false</all>
</requireActiveProfile>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Not choosing a profile would cause the build to fail and output the <message> specified above.
Summary
We’ve configured maven profiles to build our app differently for different application servers: tomcat and jetty, thus solving the dependencies incompatibility problem. We’ve used maven-enforcer-plugin to make sure that the programmer chose a profile while building the application.