Maven Profiles — Compiling for WildFly and JBoss from the Same Codebase
How to use Maven profiles to build the same application for WildFly 17 in non-production and JBoss 4.1 in production.
This is a specific scenario but I suspect anyone who’s worked in a large enterprise long enough has hit something like it.
Production was running on JBoss 4.1. The application had been there for years. JBoss 4.1 was not going anywhere — too risky, too much testing required, change freeze, the usual reasons. But development and testing environments needed to move to WildFly 17 because JBoss 4.1 was no longer getting security patches, and the security team had opinions about that.
The constraint: the same codebase had to compile and deploy to both. The solution: Maven profiles.
The problem
JBoss 4.1 and WildFly 17 are different application servers with different bundled libraries and different classloading behaviour. Some JARs that JBoss provided on the classpath, WildFly either didn’t provide or provided at a different version. Some APIs that WildFly provided were not available on JBoss 4.1.
If you just changed the dependencies for WildFly, the build broke for JBoss. And vice versa.
The approach
Define the server-specific dependencies inside a Maven profile, with scope provided (since the server itself provides them at runtime). The application code stays the same; what changes is which JARs are on the compile classpath.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<profiles>
<!-- Default profile — targets WildFly 17 -->
<profile>
<id>wildfly</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-ejb3</artifactId>
<version>17.0.1.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<version>2.0.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!-- JBoss 4.1 profile — used only for production builds -->
<profile>
<id>jboss41</id>
<dependencies>
<dependency>
<groupId>jboss</groupId>
<artifactId>jboss-ejb3</artifactId>
<version>4.2.3.GA</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<!-- JBoss 4.1 runs on JDK 6 -->
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Building for each environment:
1
2
3
4
5
# WildFly build (default, for dev and test)
$ mvn clean package
# JBoss 4.1 build (explicit, for production)
$ mvn clean package -P jboss41
What about code that genuinely differs?
Sometimes there’s code that can’t compile against both server APIs — not just dependency differences, but actual API differences. One way to handle this: use a common interface in shared code, with server-specific implementations in separate source directories.
1
2
3
4
5
6
7
src/
main/
java/ # shared code
wildfly/
java/ # WildFly-specific implementations
jboss41/
java/ # JBoss-specific implementations
Configure each profile to add the appropriate source directory:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<profile>
<id>wildfly</id>
<build>
<sources>
<source>src/wildfly/java</source>
</sources>
</build>
</profile>
<profile>
<id>jboss41</id>
<build>
<sources>
<source>src/jboss41/java</source>
</sources>
</build>
</profile>
Only the activated profile’s source directory gets compiled. The resulting jar contains only the implementation relevant to that server.
The benefit beyond the immediate problem
The thing this setup bought us was time. The testing team could thoroughly test changes against WildFly while production continued running on JBoss 4.1 unchanged. New features went through the full testing cycle on WildFly. Production deployments continued on JBoss 4.1 until the migration was ready.
Without the profiles, the options were: deploy untested WildFly builds to production (not a conversation I wanted to have), or maintain two entirely separate codebases (nobody wanted that), or stop developing until the migration was complete (also not popular).
Profiles made the parallel tracks possible.