31

Java 16 introduced Records, which help to reduce boilerplate code when writing classes that carry immutable data. When I try to use a Record as @ConfigurationProperties bean as follows I get the following error message:

@ConfigurationProperties("demo")
public record MyConfigurationProperties(
        String myProperty
) {
}
***************************
APPLICATION FAILED TO START
***************************

Description:

Parameter 0 of constructor in com.example.demo.MyConfigurationProperties required a bean of type 'java.lang.String' that could not be found.

How can I use Records as @ConfigurationProperties?

Moritz
  • 1,954
  • 2
  • 18
  • 28

7 Answers7

50

Answering my own question.

The above error raises from Spring Boot not being able to construct the bean because of the lack of a no-argument constructor. Records implicitly declare a constructor with a parameter for every member.

Spring Boot allows us to use the @ConstructorBinding annotation to enable property binding by constructor instead of setter methods (as stated in the docs and the answer to this question). This also works for records, so this works:

@ConfigurationProperties("demo")
@ConstructorBinding
public record MyConfigurationProperties(
        String myProperty
) {
}

Update: As of Spring Boot 2.6, using records works out of the box and @ConstructorBinding is not required anymore when the record has a single constructor. See the release notes.

Moritz
  • 1,954
  • 2
  • 18
  • 28
7

I had the same issue with spring boot 3.0.0 and @ConstructorBinding is deprecated.

I needed to add:

@SpringBootApplication
@ConfigurationPropertiesScan
public class Application
Liviu Stirb
  • 5,876
  • 3
  • 35
  • 40
  • Although the old `@ConstructorBinding` is deprecated, the replacement annotation has the same name. So we went from `org.springframework.boot.context.properties.ConstructorBinding` to `org.springframework.boot.context.properties.bind.ConstructorBinding` https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/properties/ConstructorBinding.html – Atorian Feb 28 '23 at 10:23
  • @Atorian Yes. However, we can use the alternate annotation only on a Constructor but not on Application properties class. – Sreekar Swarnapuri Mar 18 '23 at 16:26
5

If you need to declare default values programatically:

@ConfigurationProperties("demo")
public record MyConfigurationProperties(String myProperty) { 
    
    @ConstructorBinding
    public MyConfigurationProperties(String myProperty) {
        this.myProperty = Optional.ofNullable(myProperty).orElse("default");
    }
}

java.util.Optional properties:

@ConfigurationProperties("demo")
public record MyConfigurationProperties(Optional<String> myProperty) {

    @ConstructorBinding
    public MyConfigurationProperties(String myProperty) {
        this(Optional.ofNullable(myProperty));
    }
}

@Validated and java.util.Optional in combination:

@Validated
@ConfigurationProperties("demo")
public record MyConfigurationProperties(@NotBlank String myRequiredProperty,
                                        Optional<String> myProperty) {

    @ConstructorBinding
    public MyConfigurationProperties(String myRequiredProperty, 
                                     String myProperty) {
        this(myRequiredProperty, Optional.ofNullable(myProperty));
    }
}

Based on this Spring Boot issue.

Lovro Pandžić
  • 5,920
  • 4
  • 43
  • 51
  • Do you know how you can use records to declare complex parameters and have the properties follow the hierarchical structure like it would for non record classes? – vab2048 Nov 20 '21 at 10:01
  • I don't recall anything specific about that case, doesn't regular nesting of records work? – Lovro Pandžić Nov 22 '21 at 07:32
  • For me these solutions are not working at all :/ – MrNobody Nov 23 '21 at 11:02
  • I have the exact same issue, I don't think nesting works ... – KhaledE Nov 23 '21 at 16:41
  • 1
    Indeed it doesn't work. I've created a new issue on spring boot github tracker https://github.com/spring-projects/spring-boot/issues/28797 – Lovro Pandžić Nov 24 '21 at 09:15
  • 1
    I got it to work by using `@EnableConfigurationProperties` instead of `@Configuration`. Also I did not use `@Validated` - I just did the validation in the constructor. – vab2048 Nov 26 '21 at 10:44
3

With spring boot 3.0.4, I have been able to use the records directly without any @ConstructorBinding annotation.

@ConfigurationProperties(prefix = "app")
@Validated
public record ApplicationProperties(String version, FtpProperties ftp) {
    public record FtpProperties(
                            @NotEmpty String host,
                            @Positive int port,
                            @NotEmpty String username) {}
}
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
2

ConstructorBinding and compact form of constructor in Spring Boot 3.1.0

As of 'spring-boot' version '3.1.0':

  • Annotation org.springframework.boot.context.properties.ConstructorBinding is deprecated and can't be applied to type declaration (class, interface, enum, or record), but can be applied to constructor declaration
  • Annotation org.springframework.boot.context.properties.bind.ConstructorBinding; can't be applied to type declaration (class, interface, enum, or record), but can be applied to constructor declaration

Thus, we can declare record and apply org.springframework.boot.context.properties.bind.ConstructorBinding to the compact form of constructor which is still looks concise:

@ConfigurationProperties
public record ApplicationProperties(/* Your properties here. E.g.:
                                    String prop1, 
                                    Integer prop2, 
                                    NestedConfig nestedProps
                                    */) {

    @ConstructorBinding
    public ApplicationProperties {
    }

}

Note: Don't forget to enable support for @ConfigurationProperties annotated beans in your application. E.g.:

@EnableConfigurationProperties(ApplicationProperties.class)
@SpringBootApplication
public class YourApplication {

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

}
HereAndBeyond
  • 794
  • 1
  • 8
  • 17
  • 1
    This works for me in Spring Boot 3.1.1. Its important to make sure you remove the Configuration from the record. – Mike Miller Jun 30 '23 at 20:32
0

When mapping YAML configuration using the class approach, there is no problem if you use the @Configuration annotation.

However, when switching to using record for mapping, Spring will inform you that it is a final type and cannot be proxied and managed in the container.

I have found that you can explicitly declare in the main class of Spring using @EnableConfigurationProperties(XXProperties.class) or @ConfigurationPropertiesScan that you need to add the class with the @ConfigurationProperties annotation to the container, regardless of whether it is a class or record.

In version 2.x of Spring Boot, if you want to use the record type to map YAML files, you need to add the annotation @ConstructorBinding to the class. However, in version 3.0 of Spring Boot, this is no longer necessary and it has been marked as deprecated.

https://youtrack.jetbrains.com/issue/IDEA-295483/Spring-Boot-ConfigurationProperties-annotated-java-record-is-reported-as-not-used

@SpringBootApplication
//@EnableConfigurationProperties(YmlConfigDbProperties.class)
@ConfigurationPropertiesScan
public class AppMain {

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

}
@ConfigurationProperties(prefix = "config.db")
//@Configuration
public record YmlConfigDbProperties(){}
Rysis
  • 1
0

In Spring-Boot 2.7.5 you will only have to

  1. Annotate the record with @ConfigurationProperties
  2. Annotate the Main Class with @ConfigurationPropertiesScan({RecordConfig.class}). This part is important as this tells Spring the specifics of the Config class and helps it register as a beanenter code here.
aneesh
  • 11
  • 2