Immutable @ConfigurationProperties

Is it possible to have immutable (final) fields with Spring Boot's @ConfigurationProperties annotation? Example below

@ConfigurationProperties(prefix = "example")
public final class MyProps {

  private final String neededProperty;

  public MyProps(String neededProperty) {
    this.neededProperty = neededProperty;
  }

  public String getNeededProperty() { .. }
}

Approaches I've tried so far:

  1. Creating a @Bean of the MyProps class with two constructors
    • Providing two constructors: empty and with neededProperty argument
    • The bean is created with new MyProps()
    • Results in the field being null
  2. Using @ComponentScan and @Component to provide the MyProps bean.
    • Results in BeanInstantiationException -> NoSuchMethodException: MyProps.<init>()

The only way I have got it working is by providing getter/setter for each non-final field.


Solution 1:

From Spring Boot 2.2, it is at last possible to define an immutable class decorated with @ConfigurationProperties.
The documentation shows an example.
You just need to declare a constructor with the fields to bind (instead of the setter way) and to add the @ConstructorBinding annotation at the class level to indicate that constructor binding should be used.
So your actual code without any setter is now fine :

@ConstructorBinding
@ConfigurationProperties(prefix = "example")
public final class MyProps {

  private final String neededProperty;

  public MyProps(String neededProperty) {
    this.neededProperty = neededProperty;
  }

  public String getNeededProperty() { .. }
}

Solution 2:

I have to resolve that problem very often and I use a bit different approach, which allows me to use final variables in a class.

First of all, I keep all my configuration in a single place (class), say, called ApplicationProperties. That class has @ConfigurationProperties annotation with a specific prefix. It is also listed in @EnableConfigurationProperties annotation against configuration class (or main class).

Then I provide my ApplicationProperties as a constructor argument and perform assignment to a final field inside a constructor.

Example:

Main class:

@SpringBootApplication
@EnableConfigurationProperties(ApplicationProperties.class)
public class Application {
    public static void main(String... args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

ApplicationProperties class

@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {

    private String someProperty;

    // ... other properties and getters

   public String getSomeProperty() {
       return someProperty;
   }
}

And a class with final properties

@Service
public class SomeImplementation implements SomeInterface {
    private final String someProperty;

    @Autowired
    public SomeImplementation(ApplicationProperties properties) {
        this.someProperty = properties.getSomeProperty();
    }

    // ... other methods / properties 
}

I prefer this approach for many different reasons e.g. if I have to setup more properties in a constructor, my list of constructor arguments is not "huge" as I always have one argument (ApplicationProperties in my case); if there is a need to add more final properties, my constructor stays the same (only one argument) - that may reduce number of changes elsewhere etc.

I hope that will help