> In the course of using this feature in the `jextract` project, we did learn quite a few things we didn’t already know, and this was conclusive enough that it has motivated us to adjust our approach in this feature. Specifically, the role of processors is “outsized” to the value they offer, and, after further exploration, we now believe it is possible to achieve the goals of the feature without an explicit “processor” abstraction at all! This is a very positive development.
My Summary:
- nothing to do with the choice of backslash vs dollar. Doubles down on dollar being bad (due to backward and cross compat with other languages)
- original choice to use "Template Processors" misguided, reverses course to StringTemplate Literals
- this requires a new paradigm where APIs now need to explicitly support StringTemplates, instead of relying on the Processor to convert them into Strings as necessary (big
leap in my logic here)
- StringTemplates are explicitly still not Strings. This is in line with their "Security Focus First"
- sensitive APIs that depend on StringTemplates should explicitly treat their interpolated results carefully through overloads in a sensitive context.
Obviously I don't know much about the `jextract` project but I can guess 1 possible way this breaks the security concept is if you use the wrong Processor to pass into a Db.query method, for example.
db.query(STR."select * from users where id = \{id};");
This way, you can still do it, but its just much harder to do. Or as an API developer you could deprecate your String overload and insist only on StringTemplate usage from now on.
db.query("select * from users where id = \{id};"); // this is compiled as a StringTemplate
db.query("select * from users where id = \"" + id "\""); // this is still a String, and it's still bad, but can be mitigated by deprecating Stringm method
Disclaimer: i haven't done java in nearly 10 years... i just love theorycrafting. So take my contribution with a pinch of salt please :)
That's actually a really clever mechanism. Although it'll make the common case (basic string interpolation, no need for security concerns) more complicated unless a lot of APIs support a StringTemplate overload.
It's not just you. My reading is that the flaw was toxic Java users made the developers feel so bad about the feature that they canceled it out of spite. I'm almost certain that's not what it was but that's the only information in there.
This comment is *not* an explanation of why the feature was problematic in its current form, and portraying it that way makes the JDK team look far more political than they actually are. The comment is just explaining why feedback from usage is better than comments just looking at syntax.
A sibling comment links to the actual technical discussion and reasoning.
I understand where you are coming from, but it is a very good signal for the team. The easiest way to get the desired feedback would probably be to adequately address to the current community concern.
It is very well addressed, in the right forum (on the OpenJDK mailing list). It's a bit uncharitable to cherry pick a reddit comment on a specific thing and portray that as the public messaging on the technical decision.
I want Brian et al. to comment more on reddit threads, not less, but this sort of interpretation is a reason for him not to.
I'm not taking a particular stance on the JEP. I am not sure what is being cherry picked. The comment explicitly called out the customers for creating antihelpful noise. In my eyes, that tells me there is deep value misalignment between the involved parties, which is worth reflecting on.
Not getting in JDK 23 release as a "preview future" (as was previously expected), because the design is not finalized yet, and the "preview" thing is to test drive mature designs ready to be in the language.
It's not cancelled, and it's not even like the Go Generics situation, where the feature was shot down again and again due to endless deflection and bikeshedding. It will most likely make it in next round, which isn't that far in the future.
Yes, hence me using the past tense ("was shot down again and again").
>They took the time to get them right, but then they did
I'd say thet just took the time to bikeshed, and ended up not with the "right solution", but with a Not-Invented-Here remake of the Generics wheel with bizarre warts.
I've been following the JEPs from a distance and my feeling was that there wasn't really a "wow!" or great solution and a lot of mild disagreements.
Maybe it's a bikeshedding situation, but since we are talking about language design and one with a huge instaled base I think this was the best decision since the upside is really not that great compared to the downside.
This is a well solved problem in languages like JS[0] and C#[1] allowing both plain and customized interpolation. Perhaps they wanted a different implementation and could not just take after prior art due to politics or culture?
(example showcases it better than API spec, C# generally lets you take it further to perform zero-allocation interpolation with custom interpolated handlers if you e.g. want to pass interpolated output into a buffer/stream/etc.)
It's strange as they point out that doing what Scala does is not possible as the language already has string literals that could contain the $ syntax stuff. But Scala does not enable string interpolation without explicitly using it by adding s infront of the string (s"some $value ${another}way")
Though as they point out the syntax was not really the problem.
This was always going to fail because the requirements were insane: solving SQL, XML and JSON generation should have never been part of the spec. There are hundreds, if not thousands of libraries for the those cases.
The developers should have never tried to boil the ocean: simple Kotlin or Scala style interpolation with overloading `+` for concatenation is the 80% use case here. Yes it doesn’t do everything. But it makes life a lot more ergonomic. And they rejected this solution out of hand! So instead of something useful but not perfect we get nothing.
>Perhaps I can shed some light on this apparent contradiction.
>We discovered, unfortunately quite late in the game, that the design was flawed. How was this discovered? By actually using it in a substantial, real-world internal project. But these flaws had little to do with "someone didn't like the syntax"; they were much more fundamental to the model.
>Unfortunately, most of the feedback we received on public channels was of the "someone didn't like the syntax" variety (at varying levels of constructiveness, not all with positive coefficients). It did not derive from actual experience or from deep analysis; the supporting arguments provided were emotional, not technical; and it merely re-trod, with little new perspective, already-explored ground. It was literally a thousand drive-by, "blink" responses.
>The net effect of this was not only unhelpful, but anti-helpful. I get that every developer has opinions about syntax, and seemingly some fraction of them will literally explode if they don't express it out loud, but such discussions are like an invasive species: they suck up the oxygen that is required for actually useful feedback to survive. (Or maybe the useful feedback was buried in there, but just got lost in all the noise.)
>So the answer to your implied question is that its possible to be both: the feature can be discovered to be flawed, while at the same time the publicly available complaints were "nothing", as you say. It is sad, but possible.
I like the direction of just having the templates being a separate object that you can use as parameters to another method, it removes that extra syntax that was more python-oriented than java.
What I still don't like is the, apparently, automatic conversion of strings to templates? It's confusing because it's not clear in the new code how a template is identified. Just because it contains the \() part? But isn't one of the jep goals to allow apis to "allow Java libraries to define the formatting syntax used in string templates"? What if they used the backtick as a way to define a template? So that "abc \(f)" is a string but `abc \(f)` is a template?
This will force developers to explicitly choose which one to use, but since automatic conversion is rejected, I think this is what makes more sense (plus libraries can decide with overloading to admit one, the other, or both).
I can't imagine not having this. All of the web development I do today relies heavily on server-side string interpolation (and verbatim) features.
Being able to do things like evaluate functions and iterate collections inline with HTML templates has allowed me to sidestep a lot of pain over the years.
> Being able to do things like evaluate functions and iterate collections inline with HTML templates has allowed me to sidestep a lot of pain over the years.
Every single templating engine supports this. It doesn't need to be built into the core language. Here's an example from JTE [0]:
@import org.example.Page
@param Page page
<head>
@if(page.getDescription() != null)
<meta name="description" content="${page.getDescription()}">
@endif
<title>${page.getTitle()}</title>
</head>
<body>
<h1>${page.getTitle()}</h1>
<p>Welcome to my example page!</p>
</body>
Many of the proposed syntaxes for this feature were absolutely bonkers, nearly as bad as the ones proposed for raw strings. The JVM is technically impressive, but the syntax is almost C++ levels of terrible
I have yet to see the real-world project where a nicer interface to the JVM makes the difference. Not saying that they don't exist, just that they are way less than one would assume.
Yep. Just like Clojure, Scala and others, depending on the flavor. Just language-wise there is little reason for Java. But tooling and ecosystem are of course also relevant.
That's purely your choice on the libraries you use. Nothing in the language is forcing you to do this. Getter/setters are just a requirement of JPA which you don't have to use. In fact if you just use jOOQ or query dsl you can map to Java records.
The language isn't forcing it, but idiomatic Java (and decades of Java-based university training) is. Pretty much every professional Java project I've ever seen is full of code like:
Kotlin's approach, where you declare and instantiate a property, and the getter/setter/backing storage are synthesized for you, is vastly preferable to me:
I just disagree. This is not idiomatic Java; it's idiomatic JPA. All of the Java I see professionally and in the community uses immutable classes when not dealing with JPA entities.
I guess you live in a more modern workspace than I do. I just picked an open source project that (A) I know we use at work, and (B) I know is written in Java. I picked a random source file that sounded like it would have some mutable state, and the very first functions below the constructor were a trivial getter and setter around a private ivar.
I'm a little surprised it was in literally the first place I looked, but I'm not surprised that it was easy to find. "Use private ivars and write getters and setters to encapsulate your state" was probably the second thing that a generation of CompSci students learned in their first programming class, right after "an object is an instance of a class."
You picked a project that (A) is literally older than the sun, (B) targets Java 11, a version from 5 years ago, and (C) the file is generated.
Right at the top of the file...
> /* QueryParser.java /
/ Generated By:JavaCC: Do not edit this line. QueryParser.java */
I'm not claiming you're not going to see properties written out. I'm just saying that's not the majority of code you're going to see and to claim that idiomatic Java is all getters and setters is a bit far fetched.
Here is what I would consider modern idiomatic Java, immutable records and immutable classes that may expose some of their state:
Kotlin’s approach was a necessity to interact with a Java convention. Java instead tries to move away from that mutable convention to an immutable-preferring one with clearer rules (e.g. what is the correct setter and getter for an `IP` field?).
I prefer Kotlin over Java as well.
I use it at work (where Java code has a tendency to become Kotlin code if it comes close to me) and at home. I'm looking forward to seeing how the native and wasm backends might extends the reach of Kotlin in the future.
Because Kotlin as a mess language and have poor IDE support.
$ kotlin
Welcome to Kotlin version 1.6.10 (JRE 21.0.1+12-Debian-2)
Type :help for help, :quit for quit
>>> val b=ByteArray(1);
>>> b[0] == 0;
error: operator '==' cannot be applied to 'Byte' and 'Int'
b[0] == 0;
^
$ jshell
| Welcome to JShell -- Version 21.0.1
| For an introduction type: /help intro
jshell> byte[] ba={0}
ba ==> byte[1] { 0 }
jshell> ba[0]==0
$2 ==> true
Can you share plugin for NetBeans? Last time I checked it was some outdated plugin from JetBrains (who, obviously, aren't interested in any other IDE).
It's definitely not limited to the server side of things. Obviously it's the standard for Android app development and, largely, a pleasure to work with.
It is a source of so many security issues. Retracting a JEP that does not have broad support is the sane thing to do. There are enough languages with features that have not had every kink solved before standardisation. It is always a source of pain.
Both are sources of vulnerabilities, of course. But I believe a nice string interpolation implementation can allow for validation (like the java one did). That means that whenever you are working with texts, a library can enforce constraints that in the case of concatenation would have meant a domain specific API for each library.
My message isn't pick one over the other it is that whatever you pick, make sure it isn't a hack that people will regret using.
I was just showcasing that you can have both - there is regular string interpolation and there are APIs to accept interpolated expressions and then handle them arbitrarily (you can't pass a regular string produced by an interpolation to FromSql(expr), only to FromSqlRaw which makes IDE yell at you to make sure you know what you are doing), without even having to materialize the string if that isn't the intention.
Java ended up having neither which seems rather extreme.
Are you implying that other new features are not useful? Could you elaborate, because I think a lot of the stuff being added (records, pattern matching) is super useful.
The @ directives are ugly - also the beauty of kotlinx.html is it's just code, so you get everything that the IDE provides for free. You shouldn't need a different type of file for this.
> That's just such a Java thing to do. "Oh we failed to design the perfect solution, so we are not going to change anything until we find that perfect solution."
Disagree.
* Lambdas shipped without playing nice with checked exceptions
* Streams shipped while failing to terminate in cases where they should. (Do they still? I try it out every few years and so far I've been able to produce an infinite-loop or stack/heap space problem when asking for the first number of a well-defined series.)
* CompletableFutures shipped with this gem (still current I believe):
Spot the unused bool. They've also pivoted away from CompletableFutures towards green threads. Which also reportedly lock up under some thread-starving scenarios (but shipped anyway.)
* Not to mention all the newer features that I haven't kept up with that enter "preview status 1", "preview status 2", etc.
Can you give an example for streams not terminating when they should?
As for that cancel method, why is it a problem if it doesn’t use a variable? Like, there are also no-op methods inside standard libraries, these are very different to how you write ordinary code.
Also, Futures and green threads are not something to pivot between, they are different abstractions. And virtual threads locking up is pretty overblown of an issue, and always happened under some stupid workload, where normal threads would have locked up just as well.
Does java ship bugs? Of course, there is zero chance of shipping such a large project without bugs. But I do think that they have a tendency to re-think and wait for new features, before committing to them, and they actually commit to such decisions, remarkably so.
Haha I bet it was! "Java great" according to Java.
Anyway, it's a been a while, so, I downloaded the latest Adoptium - that's the one you don't get sued for using right?
./javac --version
javac 21.0.3
Full source code for pulling the first number out of a big stream:
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
Stream.iterate(0, a -> a + 1)
.flatMap(b -> Stream.iterate(b, c -> c + 1)
.flatMap(d -> Stream.iterate(d, e -> e + 1)))
.limit(1L)
.findFirst()
.get();
}
}
Run it:
./javac Main.java && time ./java Main
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.lang.Integer.valueOf(Integer.java:1073)
at Main.lambda$main$2(Main.java:7)
{...}
at java.base/java.util.stream.ReferencePipeline.findFirst(ReferencePipeline.java:647)
at Main.main(Main.java:9)
real 0m9,911s
user 1m31,992s
sys 0m1,423s
I’m yet to forgive them for type erasure. And optionals. But I stopped using Java about 5 years ago, after 20 years of being increasingly frustrated with it, and I couldn’t be happier.
I could agree or disagree depending on how 'type erasure' is defined.
I'm firmly in the static-typing camp: Do the type-checking. Use the type information to generate good code, then throw away the types.
> And optionals.
What's your beef with Optionals? They didn't exactly go all-in on it. All the standard library stuff still returns nulls. I have one or two small beefs with Optionals, but my current peeve is IntelliJ warning me that I use them. (I KNOW I use them! I'm telling my callers which parameters they can include and which ones they can leave out.)
The thing with type erasure is that reflection is a big part of Java - but it doesn’t work too well with generics.
With optionals, I had hoped they would be a huge benefit to catching potential NPEs at compile time. But it was just lipstick. It was half assed. The reasoning was as you say - telling the caller about nulls - but IMO it adds complexity without benefit. They could have just defined a standard annotation. Or… made the compiler enforce them.
While it’s more how they are used, imo optional is abused to the point it adds complexity. You don’t avoid handling null, but now you have to deal with present or not. Therefore you’ve gone from 2 states to 3. Use of value or null is simpler to me and use of optional can be via Optional.of() rather than parameters and properties for the vast majority of cases.
On the one hand, it is super hacky and requires ugly workarounds (passing Class<T> references around). At the same time, it also made creative abuses of the type system possible (e.g., heterogeneous lists). All while remaining type safe (albeit with runtime type checks).
It is also very noticeable how Java can iterate more quickly on its type system (only partially embedded in its VM) whereas C# generics are more or less set in stone (deeply embedded in its runtime).
What do you have in mind by "set in stone"? The emit strategy is implementation defined but pursuing true generics, particularly when using structs which are identical to Rust generics, allows C# to have optimal (monomorhpized) codegen when needed.
Java's lack of string interpolation has never been a real issue. String.format works well enough for most easy use cases, and there are plenty of templating engines for more serious work.
This JEP seemed promising at some point, but in the end it was decided that it wasn't good enough to become a language feature. And that makes sense. It's likely that a future JEP will replace it. And even if it never happens, it's still not a problem.
Introducing poor language features just "because other languages pick a point" doesn't make sense.
> String.format works well enough for most easy use cases
It's a convenience thing.
C# can also use `String.Format("My template: {0}", name)`, but `$"My template: {name}"` is much nicer, IMO.
The longer the string and the higher the number of tokens, the more it benefits. I find this particularly true with working with LLM prompts, for example.
Longer strings with more tokens are exactly where real templating engines with proper parsing/quoting/sanitizing would shine, instead of injection-prone interpolation.
In the case of prompting, a lot of the work is actually in shaping the context -- typically using data you already have from somewhere else. The prompt really doesn't care about the quoting and parsing would have been done beforehand when the content was acquired.
With respect to mitigating injections, when dealing with user entered prompt fragments, I don't think there's any templating engine that would be able to address this because it's not a standard format like SQL or JavaScript. Typically, we run the input prompt through another prompt first to test for validity of the prompt in the context of the flow and reject the prompt fragment if it's not valid for the context.
Not something a template engine is going to solve.
>That's just such a Java thing to do. "Oh we failed to design the perfect solution, so we are not going to change anything until we find that perfect solution."
That's not true at all. In the last years there were lots of features that were introduced, later disabled, enabled and then disabled again because they were causing smaller or bigger issues. There was a major work on metaspace tuning (guard allocations) that went in, stayed in for 2 or 3 versions then was removed. AVX features were enabled and disabled multiple time because companies where getting wrong results or crashes.
It's a fair game to remove something you as a developer don't consider ready. Just think how much garbage your team or company have shipped that wasn't ready, you had to patch under pressure or explain to users that it's not really working as described by marketing team and users should stick to old ways for a few more months.
There is also a proof that they can be shipped for a complex runtime without such level of pain: added back in .NET Core 3.0[0] and there were relatively few AVX-related bugs in the compiler since then and all of them were fixed in servicing (patch) releases upon discovery, without deprecation.
Do you know which CPU cores was it buggy in for a very long time? The ones that everyone uses had flawless support for more than a decade.
In either case, I don't see how that disproves the argument that support of hardware features like AVX in a runtime comparable to JVM can be done well.
(though I'm biased to say low-level features are a weak point of JVM and a strong point of .NET, you don't have to sacrifice high-level convenience in other areas to get that)
Totally agree about the "removing a misbehaving" feature part.
The frustration I was trying to express is more related to "perfect being the enemy of good". JEP-430 was introduced 3 years ago. By that point C# has already had string interpolation in place for 6 years and the C# solution achieves 90% of what JEP-430 set out to do. Java users could have benefited from 90% of that feature for 9 years by now.
It sounds a bit dramatic when we are talking about string interpolation, I realize that.
"Oh we failed to design the perfect solution, so we are not going to change anything until we find that perfect solution."
Did they think that generics were perfect? I doubt it.
Its a community that's been burned by its past pragmatism.
I think its wise to be more cautious in response.
It took 8 years for generics to be released, after they couldn’t decide how to in the initial release. So yeah, they’ll just wait until people run to other language/platforms.
The JVM is a masterwork of engineering, and I mean that without any sarcasm. Its fast, it has a pretty sophisticated GC, it has a lot of great well-tested libraries built in, I want to use it for pretty much everything, but Java still kind of feels like it's stuck in ~2008. So many languages have added quality-of-life improvements literal decades ago and Java is just barely getting them. I mean, for goodness sakes, Java didn't even get multiline strings until Java 15!
Now, before someone posts this, yes I'm obviously aware of Kotlin and the dozens of other JVM languages out there, I use Clojure all the time, but I would still prefer that the core language for the platform were caught up with the rest of the world.
It also features one of my favorite language features: after you install the jre on an OS -- and you can install multiple jres side-by-side -- you get robust, single-file deploys from megajars. Which are just big zips. And this has been working for at least 25 years.
No scribbling all over the OS, no system global packages, no shell games with bundler / virtualenv, and not much need for Docker.
(I really like rails, but the deploy story is nowhere near as good as java.)
Brian Goetz is one of the Java language&JDK architects.