I'm trying to wrap my head around how to use configuration properties functionality of Spring Boot while trying to keep things concise, robust and safe. What I'd like to have is a configuration properties definition that is: - immutable (final fields and class) - concise (records) - robust (validated) - safe (primarily null safe by using both validation and Optional).

Something like

@Validated
@ConfigurationProperties("fooBar")
@ConstructorBinding
record FooBarProperties(@NotNull Foo foo,
                        Optional<Bar> bar) {

}

Due to limitations of records of keeping exact types for accessors and parameters user is forced to use Optional on record parameter definitions. My understanding is this clashes with

The use of java.util.Optional with @ConfigurationProperties is not recommended as it is primarily intended for use as a return type. As such, it is not well-suited to configuration property injection. For consistency with properties of other types, if you do declare an Optional property and it has no value, null rather than an empty Optional will be bound.

Can support for this use case be added?

Comment From: lpandzic

Second question I had is how to use the pluggable filtering mechanism introduced in https://github.com/spring-projects/spring-boot/issues/21454? Is it automatic?

Comment From: wilkinsona

In my opinion, Optional and records aren't a very good fit, even without configuration properties in the picture, for a couple of reasons.

The JEP for records talks about them representing state and I don't think Optional really does that. FooBarProperties either has a bar or it does not. When it does, bar should be non-null and when it does not, bar should be null. To me, Optional doesn't provide a direct representation of this state but instead provides a view of the state that's one step removed. That doesn't feel right to me in a record.

Secondly, the JDK team strongly encourage using Optional only as a return type and even then discourage using it routinely as a return type for a getter:

Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors.

For example, you probably should never use it for something that returns an array of results, or a list of results; instead return an empty array or list. You should almost never use it as a field of something or a method parameter.

I think routinely using it as a return value for getters would definitely be over-use.

For these reasons, I'm not in favour of relaxing the restrictions around injecting Optional as part of configuration property constructor binding. Let's see what the rest of the team thinks.

Comment From: lpandzic

The JEP for records talks about them representing state and I don't think Optional really does that.

How come? Optional javadoc states it's a value based class and https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/doc-files/ValueBased.html explains how instances of such classes should only be comparable by state. So if value based classes don't represent state, which ones do?

FooBarProperties either has a bar or it does not. When it does, bar should be non-null and when it does not, bar should be null. To me, Optional doesn't provide a direct representation of this state but instead provides a view of the state that's one step removed. That doesn't feel right to me in a record.

I'd argue that we shouldn't encourage software engineers to use nulls in 2021 for, at least to me, obvious reasons.

Secondly, the JDK team strongly encourage using Optional only as a return type and even then discourage using it routinely as a return type for a getter: ...

I'm aware of that argument and I have yet to meet a person that isn't a JDK maintainer that it really makes sense to. On one hand their argument is that users should just get used to living with nulls and since Optional field itself can be null users shouldn't use it. On the other hand, they invented java.util.Optional and used it java.lang APIs...

My belief is that they are doing this so they have a cleaner path to inline type Optional migration where they (JDK maintainers) don't need to address this problem - null fields of java.util.Optional type.

Taking a step back from java.lang.Optional situation, would it be more acceptable if we used another type with which we could go around this problem? (guava Optional, Either...)

Comment From: philwebb

We discussed optional binding of @ConfigurationProperties previously in #21795. At the time we decided not add it because we didn't want to add complexity to the binding code and the user could still use optional themselves if they wanted.

I must admit, the use of records complicates that decision a little. I'm personally still not keen on using Optional myself, but it's a shame to put additional barriers up for those that do. What concerns me most about adding Optional support is how well it will co-exist with other features like @DefaultValue. We'll need to consider the impact quite carefully.

In the meantime, one option that you have is to add a second constructor that the binder can use. The following code seems to work OK (although it's not particularly concise):

@ConfigurationProperties("my")
public record MyProperties(String a, Optional<String> b) {

    @ConstructorBinding
    public MyProperties(String a, String b) {
        this(a, Optional.ofNullable(b));
    }

}

Comment From: lpandzic

Thanks @philwebb, I've identified one missing piece in your example with the help from @wilkinsona on Gitter.

So the end solution for my original problem would be:

@Validated
@ConfigurationProperties("fooBar")
record FooBarProperties(@NotNull Foo foo,
                        Optional<Bar> bar) {

    @ConstructorBinding
    FooBarProperties(Foo foo, Bar bar) {
        this(foo, Optional.ofNullable(bar));
    }
}

From my point of view this is good enough and this issue can be closed as far as I'm concerned.