How can I mitigate that the NoSuchBeanDefinitionException
and the related UnsatisfiedDependencyException
occur when testing a multimode Spring Boot app that has been configured with Java 9 modules?
After adding module-info.java
files to my multi module project, my Spring Boot application test started to fail:
test failure
contextLoads Time elapsed: 0 s <<< ERROR!
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'second':
Unsatisfied dependency expressed through constructor parameter 0;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'com.example.first.First' available:
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'com.example.first.First' available:
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
folder structure
parent
|
+ pom.xml
|
+-- first
| + pom.xml
| + src/main/java/module-info.java
| + src/main/java/com.example.first/First.java
|
+-- second
| + pom.xml
| + src/main/java/module-info.java
| + src/main/java/com.example.second/ApplicationConfig.java
| + src/main/java/com.example.second/Second.java
| + src/test/java/com.example.second/SecondTest.java
A Maven parent module containing just the parent pom file.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>parent</artifactId>
<version>0.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<java.version>11</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>first</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>second</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<modules>
<module>first</module>
<module>second</module>
</modules>
</project>
A simple Java module that only contains a single Java class and that does have any Spring dependencies.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>parent</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>first</artifactId>
</project>
First.java
package com.example.first;
public class First {
}
first module-info.java
module com.example.first {
exports com.example.first;
opens com.example.first;
}
The application module that depends on the first module in addition to the required Spring dependencies. It its responsible for configuring Spring beans, running the application, etc.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>parent</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>
<artifactId>second</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>first</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Second.java
package com.example.second;
import com.example.first.First;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Second {
private final First first;
public Second(First first) {
this.first = first;
}
public static void main(String[] args) {
SpringApplication.run(ApplicationConfig.class, args);
System.out.println("started");
}
}
ApplicationConfig.java
package com.example.second;
import com.example.first.First;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationConfig {
@Bean
First first() {
return new First();
}
}
SecondTest.java
package com.example.second;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SecondTest {
@Test
void contextLoads() {
}
}
mvn test
or if I attempt to debug them from within my IDE.Second#main()
, both from within the IDE as well as from command line using java -jar second/target/second-0.1.0-SNAPSHOT.jar
so dependency injection and auto wiring works in this case (the jar file has to be built without tests in this case since they are failing, e.g. mvn package -DskipTests=true
) so the First
bean is created and autowired correctly in this case.module-info.java
files are deleted, so the error seem to be related to Java modulesBased on the feedback in the comments by @xerx593, I have implemented a workaround in which I have pulled out the @SpringBootApplication
class to a separate Application
class and updated the test accordingly:
|
+-- second
| + pom.xml
| + src/main/java/module-info.java
| + src/main/java/com.example.second/Application.java
| + src/main/java/com.example.second/ApplicationConfig.java
| + src/main/java/com.example.second/Second.java
| + src/test/java/com.example.second/ApplicationTest.java
Application.java
package com.example.second;
import java.util.Arrays;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
ApplicationConfig.java
package com.example.second;
import com.example.first.First;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationConfig {
@Bean
First first() {
return new First();
}
}
Second.java
package com.example.second;
import com.example.first.First;
import org.springframework.stereotype.Service;
@Service
public class Second {
private final First first;
public Second(First first) {
this.first = first;
}
}
ApplicationTest.java
package com.example.second;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest(classes = Application.class)
class ApplicationTest {
@Test
void contextLoads() {
}
}