-
Notifications
You must be signed in to change notification settings - Fork 183
Description
Description
When running a Spring Boot application packaged as an executable JAR (bootJar), SpringModulithRuntimeAutoConfiguration fails at startup with ClassNotFoundException for @ApplicationModule annotated classes. The same application works correctly when run with bootRun or from an IDE.
Environment
- Spring Boot: 4.0.1
- Spring Modulith: 2.0.0
- Java: 24
- Kotlin: 2.2.21
- Build tool: Gradle with
bootJar
Dependencies
implementation("org.springframework.modulith:spring-modulith-starter-core")
implementation("org.springframework.modulith:spring-modulith-starter-jdbc")
runtimeOnly("org.springframework.modulith:spring-modulith-actuator")
runtimeOnly("org.springframework.modulith:spring-modulith-observability")Steps to Reproduce
- Create a Spring Modulith application with modules annotated with
@ApplicationModule - Include
spring-modulith-actuatororspring-modulith-observabilityas runtime dependencies - Build with
./gradlew bootJar - Run the JAR:
java -jar app.jar
Expected Behavior
Application starts successfully.
Actual Behavior
Application fails to start with:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'meterRegistryPostProcessor' defined in class path resource [org/springframework/boot/micrometer/metrics/autoconfigure/MetricsAutoConfiguration.class]: com.tngtech.archunit.base.ArchUnitException$ReflectionException: java.lang.ClassNotFoundException: com.example.myapp.cart.api.ModuleMetadata
...
Caused by: com.tngtech.archunit.base.ArchUnitException$ReflectionException: java.lang.ClassNotFoundException: com.example.myapp.cart.api.ModuleMetadata
at com.tngtech.archunit.core.domain.JavaClassDescriptor$From$AbstractClassDescriptor.resolveClass(JavaClassDescriptor.java:166)
at com.tngtech.archunit.core.domain.JavaClass$ReflectClassSupplier.get(JavaClass.java:2636)
...
at org.springframework.modulith.core.ApplicationModules.of(ApplicationModules.java:216)
at org.springframework.modulith.runtime.autoconfigure.SpringModulithRuntimeAutoConfiguration$ApplicationModulesBootstrap.initializeApplicationModules(SpringModulithRuntimeAutoConfiguration.java:215)
...
Caused by: java.lang.ClassNotFoundException: com.example.myapp.cart.api.ModuleMetadata
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Unknown Source)
at com.tngtech.archunit.core.domain.JavaClassDescriptor$From$AbstractClassDescriptor.classForName(JavaClassDescriptor.java:172)
Root Cause Analysis
The issue is in how ArchUnit resolves classes. SpringModulithRuntimeAutoConfiguration uses ApplicationModules.of() which internally uses ArchUnit to scan the module structure. ArchUnit calls Class.forName() to reflectively load classes.
Spring Boot's executable JAR uses a nested classloader structure where application classes are located in BOOT-INF/classes/. The standard Class.forName() call doesn't work correctly with this nested structure because it uses the system classloader rather than Spring Boot's LaunchedURLClassLoader.
This works with bootRun because the classpath is flat (no nested JARs).
Workarounds
Workaround 1: Exclude auto-configurations via environment variable
SPRING_AUTOCONFIGURE_EXCLUDE=org.springframework.modulith.runtime.autoconfigure.SpringModulithRuntimeAutoConfiguration,org.springframework.modulith.observability.autoconfigure.ModuleObservabilityAutoConfiguration,org.springframework.modulith.actuator.autoconfigure.ApplicationModulesEndpointConfiguration
Workaround 2: Use developmentOnly scope (Gradle)
// Only include in development, not in production JAR
developmentOnly("org.springframework.modulith:spring-modulith-actuator")
developmentOnly("org.springframework.modulith:spring-modulith-observability")Impact
This affects any Spring Modulith application deployed as a Docker container or any environment using the executable JAR format, which is the standard Spring Boot deployment model.
Related
- ArchUnit uses
Class.forName()inJavaClassDescriptor$From$AbstractClassDescriptor.classForName() - Spring Boot's nested JAR classloader: https://docs.spring.io/spring-boot/docs/current/reference/html/executable-jar.html