9

Given some application configuration with an unresolvable placeholder, like the following application.yml

my:
  thing: ${missing-placeholder}/whatever

When I use @Value annotations, the placeholders in the configuration file are validated, so in this case:

package com.test;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class PropValues {
    @Value("${my.thing}") String thing;
    public String getThing() { return thing; }
}

I get an IllegalArgumentException: Could not resolve placeholder 'missing-placeholder' in value "${missing-placeholder}/whatever". This is because the value is being set directly by AbstractBeanFactory.resolveEmbeddedValue and there is nothing to catch the exception thrown by PropertyPlaceholderHelper.parseStringValue

However, looking to move to @ConfigurationProperties style I noticed that this validation is missing, for example in this case:

package com.test;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties(prefix = "my")
public class Props {
    private String thing;
    public String getThing() { return thing; }    
    public void setThing(String thing) { this.thing = thing; }
}

there is no exception. I can see PropertySourcesPropertyValues.getEnumerableProperty catches the exception with the comment // Probably could not resolve placeholders, ignore it here and gathers the invalid value into its internal map. Subsequent data binding does not check for unresolved placeholders.

I checked that simply applying the @Validated and @Valid annotations to the class and field do not help.

Is there any way to preserve the behaviour of throwing an exception on unresolved placeholders with ConfigurationProperties binding?

Joe Taylor
  • 2,145
  • 1
  • 19
  • 35
  • 1
    You should but `@Validated` on th class and `@NotNull` or `@NotEmpty` on the field and for validation to work you will have to have a JSR-303 validator on your class path like `hibernate-validation`. Only adding the annotation `@Validation` yields nothing. – M. Deinum Apr 19 '17 at 12:06
  • Has anybody found a solution yet? – Sam May 14 '19 at 14:36

3 Answers3

0

Apparently there are no better solutions. At least this is kind of nicer than afterPropertiesSet().

@Data
@Validated // enables javax.validation JSR-303
@ConfigurationProperties("my.config")
public static class ConfigProperties {
    // with @ConfigurationProperties (differently than @Value) there is no exception if a placeholder is NOT RESOLVED. So manual validation is required!
    @Pattern(regexp = ".*\$\{.*", message = "unresolved placeholder")
    private String uri;
    // ...
}

UPDATE: I got the regex wrong the first time. It as to match the entire input (not just java.util.regex.Matcher#find()).

Sam
  • 899
  • 6
  • 9
0

The correct regex to pass in @Pattern annotation is ^(?!\\$\\{).+

@Validated
@ConfigurationProperties("my.config")
public class ConfigProperties {
    
    @Pattern(regexp = "^(?!\\$\\{).+", message = "unresolved placeholder")
    private String uri;
    // ...
}
Zaid
  • 41
  • 1
  • 5
-1

I had the same issue exactly 10 minutes ago! Try to add this bean in your configuration:

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        propertySourcesPlaceholderConfigurer.setIgnoreUnresolvablePlaceholders(true);
        return propertySourcesPlaceholderConfigurer;
    }
DeejonZ
  • 2,451
  • 2
  • 17
  • 19
  • Good find, but in fact that's the solution for the opposite problem. I don't want to hide the error, I want to preserve it. – Joe Taylor Apr 19 '17 at 11:54
  • 1
    you have to do that manually making your properties classes to implement `InitializingBean` and overriding the `afterPropertiesSet()` method you can check by reflection each field's value and throw an exception in case they are null. – DeejonZ Apr 19 '17 at 15:46
  • That doesn't sound very elegant, besides the properties don't end up null they end up with the placeholder values still embedded in the string. – Joe Taylor Apr 19 '17 at 16:08
  • yes I agree, not very elegant. If I find a better way I'll let you know. – DeejonZ Apr 19 '17 at 16:55
  • I cannot believe afterPropertiesSet() is the best we have :'( – Sam May 14 '19 at 14:37