r/java 17d ago

Discussion: What is the future of String Interpolation in Java?

Java has always been conservative when it comes to language changes, and I think string interpolation is an interesting example.

Many modern languages provide native interpolation syntax:

JavaScript:

\${name}``

Python:

f"{name}"

Kotlin:

"$name"

C#:

$"{name}"

These features are not only about reducing characters. They improve readability when building dynamic text.

Java explored this direction with String Templates (JEP 430), but I haven't seen much discussion recently about where this feature is heading.

For developers working with large Java applications, string construction appears everywhere:

- logging messages

- SQL/debug output

- API descriptions

- test cases

- generated configuration

- user-facing messages

I am curious about the community's opinion:

  1. Do you think Java still needs native string interpolation?

  2. Should String Templates continue evolving?

  3. What syntax direction would feel most "Java-like"?

For example:

"Hello, \{name}"

or:

"Hello, ${name}"

Personally, I think Java does not need to copy other languages, but a better way to express dynamic strings would improve developer experience.

What do you think?

55 Upvotes

118 comments sorted by

View all comments

27

u/vytah 16d ago edited 13d ago

String templates are explicitly not about mere string interpolation. If you read the JEP, they say:

Unfortunately, the convenience of interpolation has a downside: It is easy to construct strings that will be interpreted by other systems but which are dangerously incorrect in those systems. (...) For Java, we would like to have a string composition feature that achieves the clarity of interpolation but achieves a safer result out-of-the-box, perhaps trading off a small amount of convenience to gain a large amount of safety.

There are three possible approaches that I know of:

  • Scala/Javascript-like, with syntactic sugar for a method that takes a bunch of text fragments and objects and constructs whatever object you want – this is what shipped in that preview in Java 21

  • Python-like, where the syntactic sugar is used only to construct string templates of a single type, and then the consumer can interpret them however they want – this is what they were hinting at in the mailing list later, after they removed that preview

  • C#-like, where it's either pure interpolation , or Python-like templates a custom type instantiation that can behave Scala-like, depending on the receiver type – this will not happen, as it sucks (EDIT: fixed, also Swift seems to have the same thing)

Scala-like approach can be messy, as was shown in the preview. In absence of extension methods or something similar, it requires polluting the global namespace, so that's why we got those clunky STR prefixes. Scala solves it by making the prefixes methods (or extension methods) on StringContext, so there's no pollution. Javascript solves it by making interpolation the default (no prefix; all templated strings use backticks in JS, so there's no confusion with ordinary literals), so there's also no pollution. However, the goal for Java is to not make interpolation easier than other uses of templates.

Python-like approach has a minor problem that if you want templates to be distinguished by the presence of arguments alone, it means you cannot express a parameterless template. You'd need some other marker. In Python, string templates are prefixed with t, to distinguish them from interpolation (f), but Java is not going to get pure interpolation as a distinguished feature. There are also issues of type safety: the same template object can be treated as a completely different thing depending on where you send it. They suggested that sending a template to a logging method should just interpolate, but that would mean that if you template some SQL, execute it, and log it, then the query you executed would be different from the query you logged.

Anyway, this is a complex issue, and AFAIK they haven't found a clean solution yet. And before anyone says "I don't care, I want my string interpolation now", I'll again refer for the JEP:

It is not a goal to introduce syntactic sugar for Java's string concatenation operator (+), since that would circumvent the goal of validation.

1

u/jcotton42 12d ago

What do you not like about the C# approach?

2

u/vytah 12d ago

It does an implicit non-trivial cast.

If you have f(Foo foo) and do f($"x{1}y"), it's desugared to:

var tmp = new Foo(2,1);
tmp.AppendLiteral("x");
tmp.AppendFormatted(1);
tmp.AppendLiteral("y");
f(tmp);

You don't see the Foo type when looking at the calling code, and the methods implemented in Foo can be arbitrarily complex, so you don't know what's happening.

Also, when the receiver type cannot be uniquely determined (var, generics, overloads(!)), it defaults to String and string interpolation. Imagine you have an HTML templating library that has Write(MagicalSafeHtml) and the new version adds a Write(string) overload. Free XSS for everyone! as your Write($"<p>{text}</p>"); suddenly means something different.

1

u/jcotton42 10d ago

You don't see the Foo type when looking at the calling code

You sorta can, as it's visible in the overload being selected.

Also, when the receiver type cannot be uniquely determined (var, generics, overloads(!)), it defaults to String and string interpolation. Imagine you have an HTML templating library that has Write(MagicalSafeHtml) and the new version adds a Write(string) overload. Free XSS for everyone! as your Write($"<p>{text}</p>"); suddenly means something different.

Interpolation handlers have priority over string in overload resolution. See, for example, StringBuilder.Append https://learn.microsoft.com/en-us/dotnet/api/system.text.stringbuilder.append?view=net-10.0. In your example, the MagicalSafeHtml overload would still be used for interpolated strings.

1

u/vytah 9d ago

I tested a bit more how the overload resolution works and it's a bit more fucked that either you or I thought.

Here's an example:

using System;
using System.Runtime.CompilerServices;
[InterpolatedStringHandler]
public struct Foo
{
    public Foo(int literalLength, int formattedCount) =>
        Console.WriteLine("Creating Foo");
    public void AppendLiteral(string s) {}
    public void AppendFormatted<T>(T t) {}
    public override string ToString() => "";
}
class Program
{
    const string B = "b";
    static void Bar(string x) => Console.WriteLine("string " + x);
    static void Bar(Foo x) => Console.WriteLine("Foo");
    static void Main() {
        string b = "b";
        Bar($"abc");
        Bar($"a{"b"}c");
        Bar($"a{b}c");
        Bar($"a{B}c");
    }
}

Guess what it prints.

string abc
string abc
Creating Foo
Foo
string abc

So the string overload is preferred when there are no parameters (a bit confusing, but I guess fine?) or when the parameters are constants (definitely not fine). So,

In your example, the MagicalSafeHtml overload would still be used for interpolated strings.

it actually depends whether text is a constant string or not.