Honza Zidek
Honza Zidek

Reputation: 19916

How to escape SpEL dollar signs in Spring YAML configuration?

In a Spring YAML configuration file, I need to have a parameter like

csv:
  file:
    pattern: /some/path/${app-name}.csv

where the ${app-name} is dynamically replaced in run time by the Java code, and I do not want Spring to replace it at the startup.

To achieve this, I need to escape the $ character so Spring does not interpret it as SpEL.

The following answers do not work in YAML:

I tried all the combinations, like

pattern: /some/path/\${app-name}.csv
pattern: "/some/path/\${app-name}.csv"
pattern: /some/path/#{'$'}{app-name}.csv
pattern: "/some/path/#{'$'}{app-name}.csv"

and none of them produces the variable containing the requested string, with the dollar sign but without the escape characters.

Please notice that it is YAML configuration. In YAML files, # is the line comment character, everything from this character on is ignored. And if I use \#, the \ is then passed to the string.

ADDED: There has been an Spring project open issue 9628 open since 25.06.2008:

There is presently no way to inject a ${...} expression that won't be picked up by PropertyPlaceholderConfigurer. Ideally we should be able to inject a string that contains ${...} for later use in its target bean without involvement from PropertyPlaceholderConfigurer.

Upvotes: 16

Views: 19218

Answers (8)

lcnicolau
lcnicolau

Reputation: 3878

Use a combination of empty key and dollar sign $ as default value:

csv:
  file:
    pattern: /some/path/${:$}{app-name}.csv


TIP: If you want to escape a Simple Expression in Apache Camel:

"You can also use the alternative syntax which uses $simple{ } as placeholders. This can be used in situations to avoid clashes when using, for example, Spring property placeholder together with Camel."

Upvotes: 6

flyx
flyx

Reputation: 39638

Spring currently does not offer an escaping mechanism for property placeholders, there is an open issue (opened on 25.06.2008):

There is presently no way to inject a placeholder expression (${...})

In the comments, this workaround is mentioned (I am not sure whether it works with YAML):

csv:
  file:
    pattern: /some/path/#{'$'}{app-name}.csv

Note that when used after whitespace or at the beginning of a line, # in YAML starts a comment.

Upvotes: 3

Alexandre Jacquot
Alexandre Jacquot

Reputation: 9

You need to use #{'$'} and as you use yaml you need to surround the value with double quotes:

csv:
  file:
    pattern: "/some/path/#{'$'}{app-name}.csv"

Upvotes: 0

Aylwyn Lake
Aylwyn Lake

Reputation: 2027

I've encountered a same problem. So you can resolve this by using yaml literal style symbol "|" , or by using literal_strip "|-" like following example.

application.yml

csv:
  file:
    pattern: |-
      /some/path/${app-name}.csv

Actually My problem is config a formula in yml and then dynamic resolve the expression in java. Sharing the solution here.

I choose spring el solution and use spring version 5.0.9.RELEASE.

I define a formular in yml,

score:
  formula: |-
    10 * #x + #y

Then in a spring component bean,

@Value("${score.formula}")
String scoreFormula;

At last by using spring el,

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();

context.setVariable("x", 1);
context.setVariable("y", 1);
Integer score = parser.parseExpression(scoreFormula).getValue(context,Integer.class);

reference

yaml-multi-line

Upvotes: 4

Andrej Burcev
Andrej Burcev

Reputation: 353

Actually none of the answers worked for me. However, adding a double dollar sign worked for me fine:

csv:
  file:
    pattern: /some/path/$${app-name}.csv

Upvotes: 0

Eric Lee
Eric Lee

Reputation: 9

Why not try using ${sys:$} which is ugly but effective. I think no one will use $ as the key.

Upvotes: 0

I had the same problem, i just found dumb clever solution define a property named dollarSign or ds for short.

ds: "$"

then use it like so, ${ds} will be replace by $ at runtime.

csv:
  file:
    pattern: /some/path/${ds}{app-name}.csv

it was kind of funny when it worked.

Upvotes: 7

Alex Savitsky
Alex Savitsky

Reputation: 2371

The answer really depends on how exactly you inject values into your code. Since you haven't provided it yet, I'll just list the various working techniques.

  1. You can use @Value annotation to inject your property. Since, unlike @ConfigurationProperties, @Value does SpEL evaluation, you have to escape your value.

application.yml:

csv:
  file:
    pattern: /some/path/#{'$'}{app-name}.csv

TestController.java:

@RestController
public class TestController {
    @Value("${csv.file.pattern}") private String pattern;
    @GetMapping("pattern") public ResponseEntity<String> getPattern() {
        return ResponseEntity.ok(pattern);
    }
}

A GET to /pattern would result in an output /some/path/#{app-name}.csv, just as you needed

  1. You can use @ConfigurationProperties, and here it really depends on how you structure your configuration objects. Generally, though, @ConfigurationProperties should require no escaping, as they don't support SpEL by default. The following works, though, so if your setup is different, modify it:

application.yml:

csv:
  file:
    pattern: /some/path/#{app-name}.csv

Config.java:

@ConfigurationProperties(prefix = "csv.file")
public class Config {
    private String pattern;
    public String getPattern() { return pattern; }
    public void setPattern(String pattern) { this.pattern = pattern; }
}

TestController.java:

@RestController
public class TestController {
    @Autowired private Config config;
    @GetMapping("pattern") public ResponseEntity<String> getPattern() {
        return ResponseEntity.ok(config.getPattern());
    }
}

Again, a GET to /pattern would result in an output /some/path/#{app-name}.csv

What you most likely have is some different structure in your Config.java (post the relevant code, maybe?), and this could cause the property to not be processed properly.

Upvotes: 0

Related Questions