5

I'm pulling my hair out here. I want to use a Java record for my @ConfigurationProperties, providing default values to unspecified config properties. Here is a very simple example:

@ConfigurationProperties(prefix = "myconfig")
public record LoggingProperties (
    String whatever,
    String somethingToDefault
) {
    
    public LoggingProperties(String whatever, String somethingToDefault) {
        this.whatever = whatever;
        this.somethingToDefault = somethingToDefault;
    }

    public LoggingProperties(String whatever) {
        this(whatever, "whatever was specified, but not somethingToDefault");
    }

    public LoggingProperties() {
        this("neither was specified", "neither was specified");
    }
}

It seems, if I declare a noargs constructor, spring always calls that, regardless of what I actually have in my config file (application.yml)

The above will yield an instance, that when logged shows: LoggingProperties[whatever=neither was specified, somethingToDefault=neither was specified], despite the fact that my config WAS specified.

If I delete the no-args constructor, I get an exception about No default constructor found;

If I add @ConstructorBinding to the allargs constructor I get: LoggingProperties[whatever=value from file, somethingToDefault=null]. i.e. it just called the annotated constructor, ignoring the one with 1 arg (despite that prop being declared in my yml).

I'm at a loss... Is this even possible?

EDIT: in my application.yml I have:

myconfig:
  whatever: "value from file"
BoomShaka
  • 1,571
  • 7
  • 27
  • 40
  • 1
    You can only use 1 constructor which needs to be annotated with `@ConfigurationBinding`. So no currently this isn't possible and is more or less a shortcoming of records (not being able to specify a default value). The only workaround is to implement the methods and return a default when `null`. But that kind of beats the purpose of records. So you are probably better of not using records and just stick to a class. – M. Deinum May 24 '22 at 19:22

1 Answers1

3

Praise the lord for documentation (it pays to read it)

https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.constructor-binding

Default values can be specified using @DefaultValue on a constructor parameter or, when using Java 16 or later, a record component. The conversion service will be applied to coerce the String value to the target type of a missing property.

So it seems I can skip the constructor mess, and just annotate the record fields with a @DefaultValue(value = "whatever default"), like so:

@ConfigurationProperties(prefix = "someprefix")
@ConstructorBinding
public record MyRecord (
    @DefaultValue(value = "true")
    boolean someProperty,
) {}
BoomShaka
  • 1,571
  • 7
  • 27
  • 40
  • Reminds me that I should read better as well :). – M. Deinum May 25 '22 at 05:50
  • exactly right, also in boot-3 `DefaultValue` has been extended to Collections/Maps – Eugene Oct 15 '22 at 19:15
  • "when using Java 16 or later, a record component" - Please excuse me if I am being ignorant here or missing something obvious, but what exactly is a record component, or more precisely, how would you specify a default value with it? I also searched the documentation and could not find an example – benyamynbrkyc Nov 18 '22 at 13:16
  • My example uses a record. I've updated my answer with what I ended up with. – BoomShaka Nov 22 '22 at 11:04