Programming

Spring Boot multi-module: load @Configuration in JAR

Ensure @Configuration and @Primary beans from dependencies load in Spring Boot fat JARs by packaging shared modules as plain JARs and fixing mainClass.

1 answer 1 view

Spring Boot multi-module: @Configuration from dependency (module-3) not loaded in runnable fat jar — decorator @Primary bean ignored

Project modules:

  • module-1 → Spring Boot application (packaged as a runnable fat jar)
  • module-2 → Spring Boot application (also packaged as fat jar)
  • module-3 → Spring Boot application and shared module containing Spring @Configuration and a decorator bean

module-1 depends on module-3:

xml
<dependency>
 <groupId>my.group</groupId>
 <artifactId>module-3</artifactId>
 <version>${project.version}</version>
</dependency>

In module-3, a configuration class defines a decorator for an existing bean:

java
@Configuration
public class Module3Config {

 @Bean
 @Primary
 public SomeService decoratedService(
 @Qualifier("originalService") SomeService delegate,
 @Value("${feature.enabled:false}") boolean featureEnabled
 ) {
 return new DecoratedService(delegate, featureEnabled);
 }
}

Decorator:

java
public class DecoratedService implements SomeService {

 private final SomeService delegate;
 private final boolean featureEnabled;

 public DecoratedService(SomeService delegate, boolean featureEnabled) {
 this.delegate = delegate;
 this.featureEnabled = featureEnabled;
 }

 @Override
 public void doSomething() {
 // custom logic
 delegate.doSomething();
 }
}

Spring Boot main class in module-1:

java
@SpringBootApplication(scanBasePackages = {"com.product", "com.company"})
public class Module1Application {

 public static void main(String[] args) {
 SpringApplication.run(Module1Application.class, args);
 }
}

Problem:
When running from the IDE, Spring uses the decorator correctly (the @Primary bean is created and injected). After building and running the fat jar:

mvn clean package
java -jar target/module-1.jar

the decorator bean is not created and Spring uses the original implementation instead. It seems configuration from module-3 is not loaded at runtime even though module-3 is present as a dependency and component scan should include it.

Questions:

  1. What is the correct way to ensure Spring Boot always loads @Configuration classes and beans from a dependency module (module-3) in a multi-module setup so that the decorator @Primary bean is created when running the packaged jar?
  2. Could this behavior be caused by the fat jar starting from a different main class (for example, a Start-Class coming from another dependency)? If so, how can I enforce the correct main class / Start-Class when packaging the runnable jar (for example via spring-boot-maven-plugin or Maven configuration)?
  3. Any best practices for multi-module Spring Boot packaging to avoid missing configuration or beans from dependency modules?

Spring Boot multi-module projects often hit a snag with fat JARs where @Configuration classes from dependency modules like module-3 aren’t picked up at runtime, ignoring @Primary decorator beans. The fix? Package shared modules (module-3) as plain JARs without the spring-boot-maven-plugin repackage goal, letting your main app (module-1) handle the executable fat JAR and explicitly control component scanning or imports. This keeps classes on the standard classpath, ensuring decorators like your DecoratedService get loaded every time you run java -jar module-1.jar.


Contents


The Problem in Spring Boot Multi-Module Fat JARs

You’ve got module-1 as your main Spring Boot app, depending on module-3 which holds that crucial @Configuration with a @Primary decorator bean for SomeService. In the IDE? Perfect—everything wires up, delegate gets decorated based on your feature flag. But build the fat JAR with mvn clean package and fire it up? Poof. Original service wins, no decorator. Component scan includes “com.product” and “com.company”, module-3 JAR is there as a dep, yet Spring skips Module3Config entirely.

Why the disconnect? Runtime classpath magic breaks when dependencies are themselves fat JARs. Your decorator logic—wrapping doSomething() with custom featureEnabled checks—never runs. Frustrating, right? Especially since logs might not scream about it.

This hits question 1 head-on: ensuring @Configuration and beans from deps load consistently demands rethinking how you package across modules.


Why Dependency Configurations Get Ignored

Fat JARs aren’t just zipped classes. The spring-boot-maven-plugin repackages everything under BOOT-INF/classes or BOOT-INF/lib, restructuring the classpath. If module-3 builds its own fat JAR (with its own launcher structure), its classes nest inside your module-1 fat JAR. Spring’s component scanner? It hunts the unpacked classpath first. Nested BOOT-INF? Invisible unless explicitly launched via that module’s Start-Class.

And here’s the kicker: multiple fat JARs mean competing MANIFEST.MF entries. Module-3 might sneak in its own main class, overriding module-1’s. Boom—wrong app boots, wrong scan base, your @Primary bean ghosts away.

For question 2: yes, mismatched Start-Class is a prime suspect. PropertiesLauncher (Spring Boot’s fat JAR loader) respects loader.main or the manifest’s Start-Class. If module-3’s sneaks in, it launches itself subtly, skipping your module-1 scan.

Community threads echo this—Reddit devs nailed it: “fat JAR mechanism restructures so classes aren’t in their usual place.”


Fix 1: Package Module-3 as a Plain JAR

Don’t make every module a runnable app. Shared ones like module-3? Plain JARs only.

In module-3’s pom.xml, keep spring-boot-starter but skip repackage:

xml
<build>
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 <configuration>
 <skip>true</skip> <!-- No fat JAR for libs! -->
 </configuration>
 </plugin>
 </plugins>
</build>

Now mvn package on module-3 spits a standard JAR—classes at root, no BOOT-INF nonsense. Module-1 pulls it in normally, classes land on classpath directly. Scanner finds Module3Config. Decorator @Bean fires. Done.

Baeldung spells it out: “For library modules, skip repackaging… ensures classes from dependencies are directly on the classpath.”

mvn reactor rebuild (mvn clean package) propagates this. Your fat JAR now has module-3 unpacked and scannable.


Fix 2: Enforce the Correct Start-Class in Main Module

Module-1’s pom.xml needs to own the executable:

xml
<plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 <configuration>
 <mainClass>com.product.Module1Application</mainClass> <!-- Lock it down -->
 <skip>false</skip> <!-- Only here -->
 </configuration>
 <executions>
 <execution>
 <goals>
 <goal>repackage</goal>
 </goals>
 </execution>
 </executions>
</plugin>

This sets MANIFEST.MF’s Start-Class explicitly. No deps override it. Baeldung on main classes confirms: CLI or plugin config trumps ambiguities.

Test: jar tf target/module-1.jar | grep BOOT-INF—module-3 should be in lib/ as plain JAR, not nested fat. java -jar launches your Module1Application.


Explicitly Load Module-3 Configurations

Scans can flake on large multis. Beef up module-1’s main class:

java
@SpringBootApplication
@Import(Module3Config.class) // Direct hit, no scan needed
@ComponentScan(basePackages = {"com.product", "com.company", "com.product.module3"}) // Explicit
public class Module1Application {
 // ...
}

Or properties: spring.main.banner-mode=off and actuator endpoints to list beans (/actuator/beans). Spot your decoratedService? Good.

@Import bypasses scanBasePackages entirely—bulletproof for decorators.


Best Practices for Spring Boot Multi-Module Packaging

  • One fat JAR king: Main app only. Libs plain JARs. Medium guide: “library modules build plain JARs without executable config.”
  • Parent POM: Centralize spring-boot-starter-parent, plugin versions.
  • Profiles: Dev runs everything; prod skips repackage on libs via <profile>.
  • Verify JARs: jar xf module-1.jar BOOT-INF/lib/module-3*.jar then jar tf inside—no BOOT-INF in deps.
  • Gradle alt? bootJar { enabled = false } on libs.
  • Avoid module-2/3 as “Spring Boot applications” unless truly standalone—most shared configs aren’t.

GeeksforGeeks sums it: “For shared modules, plain JAR packaging avoids repackaging the module as executable.”

Scale to microservices? Dockerize main fat JAR, libs as Maven coords.


Step-by-Step Example

  1. Update module-3 pom: Add <skip>true</skip> to plugin.
  2. Module-1 pom: <mainClass>com.product.Module1Application</mainClass>.
  3. Main class: Add @Import(Module3Config.class).
  4. mvn clean install (reactor builds deps first).
  5. java -jar target/module-1.jar.
  6. Curl /actuator/beans—see decoratedService @primary.

Full module-3 snippet:

xml
<packaging>jar</packaging> <!-- Explicit -->

Works for your setup—featureEnabled pulls from props, delegate qualifies fine.


Troubleshooting Common Issues

Beans missing post-fix? mvn dependency:tree—ensure module-3 unpacks right. Logs quiet? --debug on java -jar.

Wrong main? jar -xf module-1.jar META-INF/MANIFEST.MF—check Start-Class.

Scan fails? Wider basePackages or @EnableJpaRepositories if data beans.

Version mismatch? ${project.version} locks it. Reactor order matters—clean always.

IDE vs JAR diff? IDE classpaths everything flat; JARs nest. Match with bootRun.

Still stuck? Actuator + debug logging: logging.level.org.springframework=DEBUG.


Sources

  1. Baeldung - Difference Between spring-boot:repackage and Maven package
  2. Baeldung - Spring Boot: Configuring a Main Class
  3. Reddit - Add one Spring Boot project as a dependency to another
  4. Medium - Building a Multi-Module Project with Spring Boot and Maven
  5. GeeksforGeeks - Spring Boot Dependency Management

Conclusion

Nail Spring Boot multi-module fat JAR woes by treating module-3 as a plain JAR dependency—skip repackage, lock Start-Class in module-1, and explicitly @Import if scans waver. Your @Primary decorator will fire reliably, from IDE to prod. Follow these, and you’ll dodge classpath pitfalls forever. Scale confidently; it’s the pro way.

Authors
Verified by moderation
Moderation
Spring Boot multi-module: load @Configuration in JAR