Search code examples
javaspringspring-bootconfigurationjava-record

Java 17 Record and ConfigurationProperties are not working


I hope is there someone who could help me to understand this error.

My problem

I have a problem usign Record with @ConfigurationProperties: when I start the application I get this error:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'environmentConstants' defined in file [C:\Workspace-IntelliJ\TEST\record-configuration-properties-test\target\classes\it\test\recordconfigurationpropertiestest\model\EnvironmentConstants.class]: Unsatisfied dependency expressed through constructor parameter 0: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

What I tried:

In Springboot 3.2.4 with Java 17 I was looking for the correct way to expose configuration values from application.yml.
I need a way to expose them and to be immutable, in a more clean way than using @Value annotation.
I found out it is possible with @ConfigurationProperties annotation and a Record, so to try it I created a new spring project with spring initializer with the only dependencies of spring-boot-starter, spring-boot-starter-test and lombok.
Then I created my configuration:
my application.yml:

app:
  constants:
    my-constant: 'MyValue'

and my Record:

@ConfigurationProperties("app.constants")
@Component
public record EnvironmentConstants (
    String myConstant){
}

and I was expecting, like the tutorial said, to be able to use it everywhere, so I created a test like this:
my test:

@SpringBootTest
@Slf4j
class ConfigurationTest {
    @Autowired
    private EnvironmentConstants environmentConstants;

    @Test
    void test(){
        log.info("TEst: {}", environmentConstants.myConstant());
    }
}

Other information:

I also tried a few of different configuration but none of them worked correctly. First: I tried to modify my configuration in application.yml like this:

app:
  environment-constants:
    my-constant: 'MyValue'

Second: I tried to use @EnableConfigurationProperties and @ConfigurationPropertiesScan on the Application class or in the Test Class.
Third: I tried to add the dependency

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
</dependency>

I found a lot of documentation about this, but it seems to me I'm doing this correctly, so why is it not working?

Similar example:

StackOverflow old discussion
Baeldung guide

Update with correct solution

Thank to @andy-wilkinson response I could find the solution. I left here the right configuration for everyone who get stucks in the same problem. Right application.yml

app:
  environment-constants:
    my-constant: 'MyValue'

Right Record class:

@ConfigurationProperties(prefix = "app.environment-constants")
public record EnvironmentConstants (
    String myConstant){
}

My Application.class with the right annotation:

@SpringBootApplication
@ConfigurationPropertiesScan
public class RecordConfigurationPropertiesTestApplication {

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

}

My Test:

@SpringBootTest
@Slf4j
class ConfigurationTest {
    @Autowired
    private EnvironmentConstants environmentConstants;

    @Test
    void test(){
        log.info("Test: {}", environmentConstants.myConstant());
        assertEquals("MyValue", environmentConstants.myConstant());
    }
}

Second solution

If I want to use @EnableConfigurationProperties instead of @ConfigurationPropertyScan I need to specify the class. So the Application.class need to be like this:

@SpringBootApplication
@EnableConfigurationProperties({EnvironmentConstants.class})
public class RecordConfigurationPropertiesTestApplication {

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

}

Solution

  • You were close when you tried @EnableConfigurationProperties and @ConfigurationPropertiesScan. In addition to using one of those, you also need to remove @Component from EnvironmentConstants.

    The problem with @Component is that it results in EnvironmentConstants being created as a regular Spring bean which then requires a String bean to be injected for its myConstant parameter.

    If you remove @Component, an EnvironmentConstants instance will only be created as a @ConfigurationProperties bean. The value of myConstant will then be the value of your app.environment-constants.my-constant property.