16

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.

Honza Zidek
  • 9,204
  • 4
  • 72
  • 118
  • Could you please post the code where you use the `csv.file.pattern`? I know for sure that the 3rd escaping option works, because I've used it (successfully) as well – Alex Savitsky Dec 21 '17 at 17:38
  • My test with a YAML file (application.yml) with Spring Boot has injected the value properly. I used `@Value("${csv.file.pattern}")` annotation to inject the pattern, and I've got the `/some/path/${app-name}.csv` result, just as expected. Since this doesn't work for you for some reason, your setup might be different, and I'm trying to determine what is the difference. Could be the difference in YAML library, Spring version, etc, for all I know. – Alex Savitsky Dec 21 '17 at 17:51
  • Just to clarify, while it might not be relevant how you **use** the variable after, it's definitely relevant how you **inject** it, and that's what I'm trying to ask. – Alex Savitsky Dec 21 '17 at 18:00
  • 1
    @AlexSavitsky Aha, I am not using `@Value`, but `@ConfigurationProperties`. – Honza Zidek Dec 22 '17 at 11:03
  • `pattern: "/some/path/#{'$'}{app-name}.csv"` works for me. And I used @Value annotation. Why not to use @Value? – Andrii Karaivanskyi Aug 11 '18 at 21:02

8 Answers8

7

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.

  • Thanks, it was the only thing that worked for me. Yours, gave me this idea [in a more simplified way](https://stackoverflow.com/a/71002105/4071001). – lcnicolau Feb 05 '22 at 21:29
3

Spring currently does not offer an escaping mechanism for property placeholders, there is an open issue (opened on 25.06.2008). 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.

Honza Zidek
  • 9,204
  • 4
  • 72
  • 118
flyx
  • 35,506
  • 7
  • 89
  • 126
  • As I wrote in the question, `#` in YAML causes that the rest of the line is ignored. It **can** be escaped by `\` so YAML does not interpret `#` as the line comment sign, but then the Java variable simply contains `/some/path/\#{'$'}{app-name}.csv`. – Honza Zidek Dec 22 '17 at 11:05
  • And apparently this does not seem to be high priority for Spring: **Created: 25/Jun/08**, Updated: 02/Apr/15, Days since last comment: 2 years, 38 weeks, 1 day ago :( – Honza Zidek Dec 22 '17 at 11:11
  • As I noted, `#` only starts a comment if preceded by whitespace. Which is not the case in this example. Moreover, you can put the scalar in double quotes. – flyx Dec 22 '17 at 15:37
  • When I put it into double quotes, the Java variable contains the string exactly as is in the YAML file, i.e. with the escape characters. – Honza Zidek Dec 22 '17 at 15:39
  • Um, if double-quoting leads to Spring *not* processing anything, isn't that the solution? – flyx Dec 22 '17 at 15:42
  • No, because the escape characters are *also* included in the string. – Honza Zidek Jul 14 '21 at 15:35
  • The issue https://github.com/spring-projects/spring-framework/issues/9628 is still open and not solved... – Honza Zidek Jul 14 '21 at 16:00
  • 1
    Java frameworks: *bug open for 13 years, nobody cares, still used* JavaScript frameworks: *some guy dislikes the template syntax, everyone migrates to new framework 2 weeks later* – flyx Jul 14 '21 at 16:38
  • I am getting an "Invalid interpolation format" error when I try this solution. – Sebastian Jun 22 '22 at 10:37
3

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

Aylwyn Lake
  • 1,919
  • 4
  • 26
  • 36
3

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

csv:
  file:
    pattern: /some/path/${:$}{app-name}.csv
lcnicolau
  • 3,252
  • 4
  • 36
  • 53
  • This solution works if I start Spring Boot application with embedded Tomcat, but not works if start with external Tomcat. No idea why. I end up using [IWonderHowLongANameICanTypeInH's solution](https://stackoverflow.com/a/57878092/10009424) – yejianfengblue Jun 27 '23 at 04:08
1

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.

Alex Savitsky
  • 2,306
  • 5
  • 24
  • 30
1

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

Eric Lee
  • 11
  • 1
1

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
Andrej Burcev
  • 355
  • 2
  • 7
1

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"
m02ph3u5
  • 3,022
  • 7
  • 38
  • 51