Can multiple property names be specified in Spring's @Value annotation?

11,564

Solution 1

You can use the following @Value annotation:

@Configuration
public class MyConfig {

    @Value("#{'${com.mycompany.propertygroup.propertyname:${oldconvention.propertyname:}}'}")
    private String myValue;
}

This @Value annotation uses com.mycompany.propertygroup.propertyname if it is provided and defaults to oldconvention.property if com.mycompany.propertygroup.propertyname is not provided. If neither is provided, the property is set to null. You can set this default to another value by replacing null with another desired value.

For more information, see the following:


As an alternative, you can capture both values and do a selection before returning the value:

@Configuration
public class MyConfig {

    @Value("${com.mycompany.propertygroup.propertyname:}")
    private String newValue;

    @Value("${oldconvention.propertyname:}")
    private String oldValue;

    public String getValue() {

        if (newValue != null && !newValue.isEmpty()) {
            // New value is provided
            System.out.println("New Value: " + newValue);
            return newValue;
        }
        else {
            // Default to the old value
            return oldValue;
       }
    }
}

Solution 2

Using SPEL is the best way to solve this. This should work

@Value("#{'${com.mycompany.propertygroup.propertyname}' != null ? '${com.mycompany.propertygroup.propertyname}' : '${oldconvention.property}'}")
private String myValue;
Share:
11,564

Related videos on Youtube

AForsberg
Author by

AForsberg

Updated on September 15, 2022

Comments

  • AForsberg
    AForsberg over 1 year

    I'm already familiar with the base behavior of Spring's @Value annotation to set a field to the value of a project property like so:

    Project's Property File

    foo.bar=value
    

    Project's Configuration Class

    @Configuration
    public class MyConfig {
        @Value("${foo.bar}")
        private String myValue;
    }
    

    However I'm trying to make a SpringBoot starter project with conditional configuration and would like to standardize the property names to something useful such as "com.mycompany.propertygroup.propertyname", but to ease transition and encourage adoption, I want to support the old property names for a time as well, and was thus wondering if there was some way to allow multiple property names to set the same field? For instance:

    My Theoretical Starter's Config

    @Configuration
    public class MyConfig {
        @Value("${com.mycompany.propertygroup.propertyname}" || "${oldconvention.property}")
        private String myValue;
    }
    

    Project A's Property

    oldconvention.property=value
    

    Project B's Property

    com.mycompany.propertygroup.propertyname=value
    

    I can't seem to find any documentation or SO answers on whether or not this is possible and how to achieve it if so... So I'm wondering if it is possible, or if it's not, is there an alternative to the @Value annotation that can be used to achieve the same effect?

    Edit to Clarify: I would not want to keep track of multiple values so I do not need instruction on how to get multiple values... the objective is to consolidate into a SINGLE VALUE that which may have multiple names. In practice, it would only ever have one name-value per project that uses the starter... only in rare cases when someone perhaps forgot to delete the old property would each property name be used (and it would probably have the same value anyway). In such cases, the NEW CONVENTION NAME-VALUE WOULD BE THE ONLY ONE USED.

    Update

    While the SpEL expression answers provided works when both properties are present, the application context cannot load when only one of the property names is present. Example:

    Updated Configuration Class

    @Value("#{'${com.mycompany.propertygroup.propertyname}' != null ? '${com.mycompany.propertygroup.propertyname}' : '${oldconvention.propertyname}'}"
    private String myProperty;
    

    Updated Property File

    com.mycompany.propertygroup.propertyname=somevalue
    

    Error

    Caused by: java.lang.IllegalArgumentException: 
    Could not resolve placeholder 'oldconvention.propertyname' in value
    "#{'${com.mycompany.propertygroup.propertyname}' != null ? '${com.mycompany.propertygroup.propertyname}' : '${oldconvention.propertyname}'}"
    

    Requiring both property names to be present defeats the purpose, which is to allow an implementing project to configure this starter using EITHER the old convention OR the new convention...

    Another Update...

    I've been playing around with the SpEL expression a bit, and I've got the conditional check working when the property is present and when it's not, but I'm having trouble with property resolution after the fact. I think the problem is because property defaults and complex SpEL expressions don't play nice together.

    @Value("#{${com.mycompany.propertygroup.propertyname:null} != null ? '${com.mycompany.propertygroup.propertyname}' : '${oldconvention.propertyname}'}")
    private String myProperty;
    

    When my SpEL is written like the above, I get a cannot resolve property placeholder exception, meaning that both properties have to be present in order for the SpEL expression to evaluate. So I got to thinking, I could use the default property syntax that I've seen for resolving optional properties: @Value("${myoptionalproperty:defaultValue}")

    So below is my attempt to combine the default property resolution with the SpEL expression:

    @Value("#{${com.mycompany.propertygroup.propertyname:null} != null ? '${com.mycompany.propertygroup.propertyname:}' : '${oldconvention.propertyname:}'}")
    private String myProperty;
    

    When using the default property notation, I keep getting this error:

    org.springframework.expression.spel.SpelParseException: 
    EL1041E: After parsing a valid expression, there is still more data in the expression: 'colon(:)'
    

    and when I Googled that error, the popular answer was that properties had to be wrapped in single quotes so that they evaluate to a string... but they're already wrapped (except the first one.. I had to unwrap that one since I wanted that to evaluate to a literal null for the null check). So I'm thinking that defaults can't be used with properties when they're wrapped in a spell expression. In truth, I've only ever seen the default property set when a @Value annotation is set with just a pure property holder, and all properties I've seen used in a SpEL expression never had a default set.

    • M. Deinum
      M. Deinum about 6 years
      @Value("${com.mycompany.propertygroup.propertyname:${oldconv‌​ention.property}}") should do the trick.
  • AForsberg
    AForsberg about 6 years
    I'd prefer the Elvis operator over having to manage multiple property instances, personally... I do have a question about the Elvis operator though... if the value of the property is actually setting a boolean, how would that work if there actually is a 'com.mycompany.propertygroup.propertyname', but it's set to false, and no 'oldconvention.property' exists? Would SpEL do a falsey evaluation and end up setting it to false anyway? What if the java type was Boolean? Would it be set to null instead of the desired false?
  • M. Deinum
    M. Deinum about 6 years
    Your SpEL sample won't work as that is a value expression not a SpEL expression. For that it should start with #{ not with ${.
  • Justin Albano
    Justin Albano about 6 years
    @M.Deinum You are correct. That was a typo and it has been corrected. Thank you.
  • Justin Albano
    Justin Albano about 6 years
    @AEvans Understood. Either will work (my opinion does not necessarily apply to your case). For boolean values, I think you are right about the falsey evaluation. To avoid this, you can set the value to Boolean and then check if it is null: @Value("#{com.mycompany.propertygroup.propertyname != null ? com.mycompany.propertygroup.propertyname : oldconvention.property}"). For more information, see docs.spring.io/spring/docs/current/spring-framework-referenc‌​e/… and the Primitives section of baeldung.com/spring-value-defaults
  • AForsberg
    AForsberg about 6 years
    @JustinAlbano Thanks for the documentation links; they've been a big help! However in practice it seems like the conditional SpEL only works when BOTH properties are defined, instead of allowing a project to only specify one of the properties, the context will fail to load because the SpEL errors on being unable to do one of the property substitutions...
  • Justin Albano
    Justin Albano about 6 years
    @AEvans Try: @Value("#{'${com.mycompany.propertygroup.propertyname:${oldc‌​onvention.propertyna‌​me:null}}'}"). I've tested it with a mock application was able to get a the value of com.mycompany.propertygroup.propertyname if it is present; if not, the value of oldconvention.propertyname is used; if neither are present, null is returned. Let me know if it works for your application.
  • AForsberg
    AForsberg about 6 years
    @JustinAlbano I think this property chaining way of yours is more concise than the null check anyway. In my version, I left out the 'null' as I think it would end up evaluating as a string that spells out 'null' because the property expression is wrapped in quotes, and according to Spring's documentation, you can put nothing after a ':' in a property placeholder to represent a null or optional. I've tested my version and it works as anticipated.
  • AForsberg
    AForsberg about 6 years
    My final solution: /* Use the fully qualified name of the property if available, otherwise, * look for the old name convention for backwards compatibility. Defaults * to null if neither property is found. */ @Value("#{'${com.mycompany.propertygroup.propertyname:${oldc‌​onvention.propertyna‌​me:}}'}") private String myValue;
  • Justin Albano
    Justin Albano about 6 years
    @AEvans Makes sense. I've updated the solution to use the empty default.
  • AForsberg
    AForsberg about 6 years
    @JustinAlbano just out of curiosity, does your alternate solution need the empty defaults as well to make sure things work when only one or the other property is present?
  • Justin Albano
    Justin Albano about 6 years
    @AEvans Yes, it does. I've updated in the answer. I also added a check for isEmpty because a missing value in this scheme will result in an empty string rather than a null.