Unresolved Placeholder Validation for Spring Boot Configuration Properties
Asked Answered
S

4

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?

Springy answered 19/4, 2017 at 10:39 Comment(2)
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.Ar
Has anybody found a solution yet?Becoming
B
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()).

Becoming answered 14/5, 2019 at 14:58 Comment(0)
T
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;
    // ...
}
Tetrapod answered 11/5, 2021 at 7:2 Comment(0)
B
0

If you're using @ConfigurationProperties on Props where you may set a default value. You can use the following:

@Value("#{props.getThing()}")
String theThing;

This also works well in other SPeL contexts like Scheduled for example

@Scheduled(fixedDelayString = "#{@dbConfigurationProperties.getExpirationCheckDelayInMs()}")
void cleanup() {
...
}
Barri answered 15/9, 2023 at 4:23 Comment(0)
D
-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;
    }
Danu answered 19/4, 2017 at 11:7 Comment(5)
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.Springy
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.Danu
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.Springy
yes I agree, not very elegant. If I find a better way I'll let you know.Danu
I cannot believe afterPropertiesSet() is the best we have :'(Becoming

© 2022 - 2024 — McMap. All rights reserved.