Moritz
Moritz

Reputation: 2516

How to use @ConfigurationProperties with Records?

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?

Upvotes: 54

Views: 37121

Answers (8)

Tsvetoslav Tsvetkov
Tsvetoslav Tsvetkov

Reputation: 1176

I use Spring boot 3.2.2, so I hadn't to to add most of the things mentioned in another answers

Here is what I did:

  1. Created AppConfig, configuration class
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({KafkaProperties.class})
public class AppConfig {
   public static final String KAFKA =  "kafka";
}
  1. Created KafkaProperties, configuration prop class
@ConfigurationProperties(prefix = AppConfig.KAFKA)
public record KafkaProperties(String autoStartup,
                              Topics topics) {

    public record Topics(String name) {

    }
}

That's all I need to add and everything if working fine.

Note: need to have naming consistency in Java and application.prop/yaml file

Upvotes: 1

aneesh
aneesh

Reputation: 41

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.

Upvotes: 0

HereAndBeyond
HereAndBeyond

Reputation: 1454

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);
    }

}

Upvotes: 8

Rysis
Rysis

Reputation: 11

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(){}

Upvotes: 1

Sreekar Swarnapuri
Sreekar Swarnapuri

Reputation: 175

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) {}
}

Upvotes: 5

Liviu Stirb
Liviu Stirb

Reputation: 6075

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

I needed to add:

@SpringBootApplication
@ConfigurationPropertiesScan
public class Application

Upvotes: 16

Moritz
Moritz

Reputation: 2516

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.

Upvotes: 72

Lovro Pandžić
Lovro Pandžić

Reputation: 6380

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.

Upvotes: 7

Related Questions