Why Did String Templates Have to Die? For the Good of Us All - JVM Weekly vol. 89
Today we return to JDK - and we'll talk about new developments in Amber, Loom, and Babylon.
1. Back to the Drawing Board with String Templates
Since the deprecation of the Security Manager, there hasn't been a more controversial JEP than the various iterations of String Templates. Each proposal for the new API has faced significant pushback. Notably, the first two Preview versions failed to bring the community to a consensus, and the third Preview, initially announced as the final version, faced substantial criticism from the community. As a result, discussions on the Amber project mailing list since March culminated in an unprecedented decision: the withdrawal of the third Preview and a complete reevaluation of the functionality. Consequently, String Templates will be removed from JDK 23, necessitating a refactor of code in projects that dared to use the test version.
For a refresher on the proposed syntax, here's an example from the JEP:
String name = "Joan Smith";
String phone = "555-123-4567";
String address = "1 Maple Drive, Anytown";
String json = STR."""
{
"name": "\{name}",
"phone": "\{phone}",
"address": "\{address}"
}
""";
Last week, Nicolai Parlog discussed the issues faced by the specification creators in detail in the new Inside Java Newscast episode, What Happened to Java's String Templates
The design of string templates aimed to facilitate not just string concatenation but also the safe embedding of variables in structured languages like SQL, HTML, and JSON. However, the use of \{...}
instead of the more familiar $
symbol and the special syntax for invoking processors received criticism. Additionally, the need for string processors (STR.
in the example) for performance was eventually deemed unnecessary, upending the original concept. The mailing list discussions indicated that the extensive rework required could not be completed within the remaining 12 weeks before the JDK 23 release, leading to the decision to give it more time.
Before contesting this decision, remember that String Templates never exited the test phase. The Preview pathway, introduced in JEP 12, aims to gather community feedback and ensure features are fully refined before final release, allowing developers to experiment with new language features, APIs, or tools in real-world conditions. This iterative process helps ensure that features are robust and meet the needs of real projects when they become part of the JDK.
However, features in the Preview version may not make it to the final Java release. If a feature receives negative feedback, encounters serious technical issues, or fails to meet community expectations, it can be changed, delayed, or completely removed. Although this has rarely happened before, it seems it's time to revise those expectations.
The first withdrawal of a JEP from the Preview pathway certainly adds flavor to the testing process for new functionalities. It will be interesting to observe how this move affects the perception of such features. Many people, including myself, experimented with String Templates, not expecting such a radical turn of events. Although there was always a possibility that something like this could happen, the two previous previews suggested that the creators were confident. It seemed that incubation was the phase where functionalities might never be implemented. But it's better now than Java living forever with a feature that doesn't meet community standards, giving us a chance for a better-accepted variant in the future.
PS: We will return to Nicolai in the next section.
2. Why You Should Be Interested in Data-Oriented Programming
My life seems to be about observing changing programming paradigms — where today's "best practice" can become tomorrow's code smell. Remember the good old days when OOP was the golden child? Everything had to be an object, enclosed like a hermit crab in its shell. Classes, inheritance, polymorphism — you couldn't pass code review (nobody seriously considered PRs back then) if your code didn't resemble a Russian matryoshka doll.
But past I wouldn't have expected the twists along the way. Just as I got comfortable with my type hierarchy, I learned that functional programming was what defined a true engineer. Suddenly, it was all about pure functions, immutable states, and avoiding side effects like the plague. Who needs objects when you can have nicely composable functions?
Meanwhile, "True OOP" was also making its way through. Not the fake OOP from previous paragraphs, but the OOP from the old days — Alan Kay's vision, based on actors exchanging messages. And heaven forbid you let your objects become anemic.
One rule remained unchanged over the years, though — using switch statements was considered an anti-pattern. But as it happens with anti-patterns, when passed down through tribal knowledge, the original reasons get lost... and our tools evolve. You can guess where this is heading, right? We're going to talk about Data-Oriented Programming now.
Change is the only constant in life
DOP is an approach heavily promoted by the creators of the Amber project and is gaining popularity due to its pragmatism and alignment with how programs are actually created. At the heart of this philosophy is the idea of avoiding excessive reliance on objects and focusing on data structures and the functions operating on them, akin to well-known stateless Spring services. New language features like sealed classes and exhaustive switches, part of Project Amber, provide stronger tools for data flow control and polymorphism, allowing the compiler to automatically notify about code branches we might have missed. This flips the table and turns practices once considered anti-patterns — like using switches — into modern, manageable, and flexible solutions.
Nicolai Parlog recently wrote a series of articles, Data-Oriented Programming, which build on earlier ideas by Brian Goetz in Data-Oriented Programming in Java. I highly recommend both publications. However, I first encountered similar concepts in my favorite book on Domain Driven Design. Domain Modeling Made Functional by Scott Wlaschin, despite its F# samples (ugh), presents how the techniques promoted by Data-Oriented Programming can be applied in a broader project context in a very convincing way.
If you're interested in learning more about DOP, Nicolai Parlog and Adam Bien recently covered this topic in Adam's podcast. In the episode Object-Oriented Programming (OOP) vs. Data-Oriented Programming (DOP) in Java, they discussed various aspects of Data-Oriented Programming. If you like talking heads, I highly recommend it.
3. Virtual Threads Get a Solution for Pinning Issues in Synchronized Blocks
Moving on to concurrency, I have two interesting publications for you, with a little "detour" along the way.
Let's start with Why are my Java Virtual Threads Slower than the Platform Threads? by Visarut Sae-Pueng, which analyzes why virtual threads in Java might be slower than platform threads. Besides the well-known fact that virtual threads are more beneficial for IO-bound applications, rather than CPU-bound applications, the author points out that virtual threads, despite their lightness and ability to handle many tasks concurrently, can experience performance drops due to "pinning," especially with intensive use of third-party libraries. Pinning occurs when a virtual thread is attached to a platform thread due to the use of synchronous methods or native functions, leading to deadlocks and an increase in carrier threads. In such cases, virtual threads are less efficient than simply using platform threads.
Considering the issues described above, we should all closely monitor [the solutions introduced in the latest Preview versions from the Loom repository. In previous Loom versions, pinning Virtual Threads occurred in several significant cases, including synchronized blocks and methods and Object.wait()
, where the "physical" carrier thread was pinned to ensure proper monitor management. The new Early Access version revamps Object Monitor mechanisms, reducing this problem, although currently at the expense of performance in edge cases, which may not yet be optimal. The creators now ask for help in testing these changes using Java Flight Recorder and reporting any issues.
To wrap up this section, let's move on to the second promised article, Overview of JVM Threads: Understanding Multithreading in Java by Eugene Kovko, which offers an overview of different thread types used by the JVM to handle events in the virtual machine and discusses their use in the context of multithreading. The list presented is extensive enough that I suspect everyone will find something of interest, although it remains somewhat of a "trivia" article.
4. Project Babylon and Code Models Take Metaprogramming in Java to a New Level
Lastly, let's return to Project Babylon and Code Reflection. Paul Sandoz recently published an article titled Code Models, introducing tools that take Java's metaprogramming capabilities to a new level.
Starting with the basics, Code Reflection extends the reflection mechanism in Java. Unlike traditional reflection, which focuses on providing information about classes and method signatures, Code Reflection delves deeper into the actual executed code. It differs from Abstract Syntax Trees (AST), which the Java compiler uses to parse and verify code according to the Java Language Specification. While ASTs are essentially containers holding syntactic details of the source code, Code Reflection provides models (called Code models), representing method or lambda code organized in a structure with a useful API for application code transformation.
Code models are the foundation of Code Reflection. They are created by the Java compiler, stored in class files, and accessible at runtime through the Code Reflection API. They address several limitations of using AST and bytecode for reflection purposes. ASTs are too detailed, and often filled with syntactic nuances, making them cumbersome for most types of code manipulation. Bytecode, on the other hand, removes useful information such as type details and hierarchical structure, making it too abstract for detailed program analysis. Code Models strike a balance, retaining key type and structural information while omitting unnecessary syntactic details. This makes them particularly useful for various tasks requiring code analysis and transformation, such as generating derivative Java code or foreign code like GPU instructions or SQL queries.
Code Models are an example of metaprogramming tools — programming techniques that allow the creation, modification, or manipulation of code during compilation or execution. Java is not the first to adopt this approach, which has counterparts in other languages where metaprogramming takes various forms. For instance, in C++, templates allow code generation during compilation, while in Python, metaclasses and decorators enable runtime modifications. On the JVM, Groovy is known for its rich metaprogramming capabilities, allowing the creation and modification of classes on the fly. However, one of the most direct forms of metaprogramming is macros, widely used in languages like Lisp or the more modern Rust.
Returning to Paul Sandoz's article, Code Models delves into the details of Code Reflection, explaining its purpose and implementation in Java. Sandoz discusses their structure, creation process, and practical applications, showcasing the usefulness of code models in scenarios such as transforming Java code into GPU kernels or generating SQL queries. Additionally, like with the new Early Access version of Loom, Paul requests the community to test the experimental Code Models using tools like Java Flight Recorder to identify potential issues.
The overall lesson from today's edition is clear: if you want to contribute to Java, learn how to use Java Flight Recorder and provide feedback.