141

Is there any String replacement mechanism in Java, where I can pass objects with a text, and it replaces the string as it occurs? For example, the text is:

Hello ${user.name},
Welcome to ${site.name}. 

The objects I have are user and site. I want to replace the strings given inside ${} with its equivalent values from the objects. This is same as we replace objects in a velocity template.

Mahozad
  • 18,032
  • 13
  • 118
  • 133
Sastrija
  • 3,284
  • 6
  • 47
  • 64
  • 2
    Replace where? A class? A JSP? String has a format method if you just: `String.format("Hello %s", username);` – Droo Sep 07 '10 at 02:56
  • 3
    @Droo: In the example, string is like `Hello ${user.name}`, not like, `Hello %s` or `Hello {0}`. – Adeel Ansari Sep 07 '10 at 03:02
  • 3
    If you need something that looks like velocity and smells like velocity, maybe it is velocity? :) – serg Sep 07 '10 at 03:02
  • @Droo: Its not a class. I've the above text in a "String" variable and wants to replace all the occurrences of the strings inside ${} with values in the corresponding objects. for example replace all ${user.name} with name property in "user" object. – Sastrija Sep 07 '10 at 03:06
  • @serg: Yes it is a velocity code. and I wants to remove the velocity from my code. – Sastrija Sep 07 '10 at 03:07
  • Possible duplicate to: http://stackoverflow.com/q/772988/435605 – AlikElzin-kilaka Apr 19 '15 at 13:11
  • @AlikElzin-kilaka This is not a duplicate, since both posts have its own differences in requirement(though both are string replacements). Please refer the accepted answer to know what I meant. – Sastrija Jun 07 '15 at 17:35
  • Maybe a bit late, but please see jeval.sourceforge.net. I have used this in a Grails application. It is a Java library. Very useful and covers more use cases than you need. It is a Formula evaluator as well. – Vishnoo Rath Oct 31 '17 at 08:48

12 Answers12

189

Use StringSubstitutor from Apache Commons Text.

Dependency import

Import the Apache commons text dependency using maven as bellow:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-text</artifactId>
    <version>1.10.0</version>
</dependency>

Example

Map<String, String> valuesMap = new HashMap<String, String>();
valuesMap.put("animal", "quick brown fox");
valuesMap.put("target", "lazy dog");
String templateString = "The ${animal} jumped over the ${target}.";
StringSubstitutor sub = new StringSubstitutor(valuesMap);
String resolvedString = sub.replace(templateString);
JH.
  • 4,147
  • 3
  • 19
  • 20
  • 4
    Javadoc for StrSubstitutor http://commons.apache.org/lang/api-release/org/apache/commons/lang/text/StrSubstitutor.html – Paul May 18 '11 at 05:17
  • 1
    Should be `Map valuesMap = new HashMap();`. – andrewrjones Aug 21 '13 at 10:22
  • 4
    `StrSubstitutor` is now deprecated in [https://commons.apache.org/proper/commons-lang/](https://commons.apache.org/proper/commons-lang/). User [https://commons.apache.org/proper/commons-text/](https://commons.apache.org/proper/commons-text/) instead – Lukuluba Oct 09 '17 at 10:49
  • 7
    `StrSubstitutor` deprecated as of 1.3, use `StringSubstitutor` instead. This class will be removed in 2.0. Gradle dependency for importing `StringSubstitutor` is `org.apache.commons:commons-text:1.4` – Yurii Rabeshko Sep 27 '18 at 13:19
  • does it allow condition based substitution? – Gaurav Jan 31 '19 at 07:14
  • I got an error The import java.util.HashMap cannot be resolved – Hikaru Shindo Aug 05 '20 at 09:54
  • Just a remark. `StringSubstitutor` instance is created with a substitution map and then parses template strings with its `replace` method. That means it cannot pre-parse the template string, so processing the same template with different substitution maps may be less efficient. – Nick Legend May 09 '21 at 20:54
  • See [this answer](https://stackoverflow.com/a/67462268/10815638) for more details. – Nick Legend May 09 '21 at 23:07
154

Take a look at the java.text.MessageFormat class, MessageFormat takes a set of objects, formats them, then inserts the formatted strings into the pattern at the appropriate places.

Object[] params = new Object[]{"hello", "!"};
String msg = MessageFormat.format("{0} world {1}", params);
RealHowTo
  • 34,977
  • 11
  • 70
  • 85
  • 10
    Thanks! I knew java should have an inbuilt way to do this without having to use are freaking template engine to do such a simple thing! – Joe M Jan 20 '14 at 01:15
  • 5
    Seems like String.format can do anything this can do -- https://stackoverflow.com/questions/2809633/difference-between-messageformat-format-and-string-format-in-jdk1-5 – Noumenon Nov 22 '17 at 15:28
  • 10
    +1. Be aware `format` also takes an `Object...` varargs so you can use this more terse syntax where preferable `format("{0} world {1}", "Hello", "!");` – davnicwil Feb 01 '18 at 19:17
  • 1
    It should be noted that `MessageFormat` can only be reliably used for its namesake, display messages, not for output where technical formatting matters. Numbers for example will be formatted per locale settings, rendering them invalid for technical uses. – Marnes Aug 07 '19 at 12:07
82

My preferred way is String.format() because its a oneliner and doesn't require third party libraries:

String message = String.format("Hello! My name is %s, I'm %s.", name, age); 

I use this regularly, e.g. in exception messages like:

throw new Exception(String.format("Unable to login with email: %s", email));

Hint: You can put in as many variables as you like because format() uses Varargs

artgrohe
  • 3,082
  • 2
  • 25
  • 31
  • 6
    This is less useful when you need to repeat the same argument more than once. E.g.: `String.format("Hello! My name is %s, I'm %s. Why is my name %s you ask? Well I'm only %s years old so I don't know", name, age, name, age);`. Other answers here require specifying each argument only once. – asherbret May 24 '20 at 12:10
  • 31
    @asherbar you can use argument index specifiers in the format string, e.g. `String.format("Hello! My name is %1$s, I'm %2$s. Why is my name %1$s you ask? Well I'm only %2$s years old so I don't know", name, age)` – jazzpi Aug 03 '20 at 11:26
  • 4
    @jazzpi I never knew that. Thanks! – asherbret Aug 03 '20 at 11:47
  • Very similar to MessageFormat.format – Lluis Martinez Mar 23 '22 at 16:08
  • Amen. The less external dependencies, the better. – Jose Quijada Jul 27 '22 at 00:31
20

I threw together a small test implementation of this. The basic idea is to call format and pass in the format string, and a map of objects, and the names that they have locally.

The output of the following is:

My dog is named fido, and Jane Doe owns him.

public class StringFormatter {

    private static final String fieldStart = "\\$\\{";
    private static final String fieldEnd = "\\}";

    private static final String regex = fieldStart + "([^}]+)" + fieldEnd;
    private static final Pattern pattern = Pattern.compile(regex);

    public static String format(String format, Map<String, Object> objects) {
        Matcher m = pattern.matcher(format);
        String result = format;
        while (m.find()) {
            String[] found = m.group(1).split("\\.");
            Object o = objects.get(found[0]);
            Field f = o.getClass().getField(found[1]);
            String newVal = f.get(o).toString();
            result = result.replaceFirst(regex, newVal);
        }
        return result;
    }

    static class Dog {
        public String name;
        public String owner;
        public String gender;
    }

    public static void main(String[] args) {
        Dog d = new Dog();
        d.name = "fido";
        d.owner = "Jane Doe";
        d.gender = "him";
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("d", d);
        System.out.println(
           StringFormatter.format(
                "My dog is named ${d.name}, and ${d.owner} owns ${d.gender}.", 
                map));
    }
}

Note: This doesn't compile due to unhandled exceptions. But it makes the code much easier to read.

Also, I don't like that you have to construct the map yourself in the code, but I don't know how to get the names of the local variables programatically. The best way to do it, is to remember to put the object in the map as soon as you create it.

The following example produces the results that you want from your example:

public static void main(String[] args) {
    Map<String, Object> map = new HashMap<String, Object>();
    Site site = new Site();
    map.put("site", site);
    site.name = "StackOverflow.com";
    User user = new User();
    map.put("user", user);
    user.name = "jjnguy";
    System.out.println(
         format("Hello ${user.name},\n\tWelcome to ${site.name}. ", map));
}

I should also mention that I have no idea what Velocity is, so I hope this answer is relevant.

jjnguy
  • 136,852
  • 53
  • 295
  • 323
  • This is what I was looking for. Thank you for giving an implementation. I was trying for it and getting incorrect results. :D. Anyway it solved my problem. – Sastrija Sep 07 '10 at 04:38
  • 2
    @Joe, glad I could help. It was a good excuse for me to finally practice writing some code that uses reflection in Java. – jjnguy Sep 07 '10 at 04:42
  • 1
    Nice Utility. Since java 9, you can now inline the map and pass the populated map as parameter e.g. Map.of ("user", user, "site", site), instead of creating the map separately. This will further simplify calling the "format" method. – Ari Singh Aug 15 '21 at 19:20
7

Here's an outline of how you could go about doing this. It should be relatively straightforward to implement it as actual code.

  1. Create a map of all the objects that will be referenced in the template.
  2. Use a regular expression to find variable references in the template and replace them with their values (see step 3). The Matcher class will come in handy for find-and-replace.
  3. Split the variable name at the dot. user.name would become user and name. Look up user in your map to get the object and use reflection to obtain the value of name from the object. Assuming your objects have standard getters, you will look for a method getName and invoke it.
casablanca
  • 69,683
  • 7
  • 133
  • 150
  • 1
    Heh, just saw this answer. It is identical to mine. Please let me know what you think of my implementation. – jjnguy Sep 07 '10 at 03:52
5

There are a couple of Expression Language implementations out there that does this for you, could be preferable to using your own implementation as or if your requirments grow, see for example JUEL and MVEL

I like and have successfully used MVEL in at least one project.

Also see the Stackflow post JSTL/JSP EL (Expression Language) in a non JSP (standalone) context

Christoffer Soop
  • 1,458
  • 1
  • 12
  • 24
3

Since Java 15 you have the method String.formatted() (see documentation).

str.formatted(args) is the equivalent of String.format(str, args) with less ceremony.

For the example mentioned in the question, the method could be used as follows:

"Hello %s, Welcome to %s.".formatted(user.getName(), site.getName())
Ahmad Shahwan
  • 1,662
  • 18
  • 29
2

Handlebars.java might be a better option in terms of a Velocity-like syntax with other server-side templating features.

http://jknack.github.io/handlebars.java/

Handlebars handlebars = new Handlebars();
Template template = handlebars.compileInline("Hello {{this}}!");
System.out.println(template.apply("Handlebars.java"));
Adam Wise
  • 2,043
  • 20
  • 17
2

Good news. Java will have string templates (from version 21, as a preview feature).

See the string templates proposal (JEP 430) here.

It will be something along the lines of this:

String name = "John";
String info = STR."I am \{name}";
System.out.println(info); // I am John

P.S. Kotlin is 100% interoperable with Java. It supports cleaner string templates out of the box:

val name = "John"
val info = "I am $name"
println(info) // I am John

Combined with extension functions, you can achieve the same thing the Java template processors (e.g. STR) will do.

mernst
  • 7,437
  • 30
  • 45
Mahozad
  • 18,032
  • 13
  • 118
  • 133
1

I use GroovyShell in java to parse template with Groovy GString:

Binding binding = new Binding();
GroovyShell gs = new GroovyShell(binding);
// this JSONObject can also be replaced by any Java Object
JSONObject obj = new JSONObject();
obj.put("key", "value");
binding.setProperty("obj", obj)
String str = "${obj.key}";
String exp = String.format("\"%s\".toString()", str);
String res = (String) gs.evaluate(exp);
// value
System.out.println(str);
Kan
  • 53
  • 1
  • 6
1

I created this utility that uses vanilla Java. It combines two formats... {} and %s style from String.format.... into one method call. Please note it only replaces empty {} brackets, not {someWord}.

public class LogUtils {

    public static String populate(String log, Object... objects) {
        log = log.replaceAll("\\{\\}", "%s");
        return String.format(log, objects);
    }

    public static void main(String[] args) {
        System.out.println(populate("x = %s, y ={}", 5, 4));;
    }

}
Jose Martinez
  • 11,452
  • 7
  • 53
  • 68
0

There is nothing out of the box that is comparable to velocity since velocity was written to solve exactly that problem. The closest thing you can try is looking into the Formatter

http://cupi2.uniandes.edu.co/site/images/recursos/javadoc/j2se/1.5.0/docs/api/java/util/Formatter.html

However the formatter as far as I know was created to provide C like formatting options in Java so it may not scratch exactly your itch but you are welcome to try :).

Mike Milkin
  • 3,961
  • 4
  • 17
  • 10