Skip to main content

Old Java versions

Java 13 introduced text blocks (multi-line strings) as a preview feature, finalized in Java 15. They make working with multi-line strings much more readable, but traditional string concatenation is still commonly found in older tests.

String concatenation

Traditional string concatenation with + operators for multi-line strings is hard to read and maintain. Each line needs explicit \n newline characters and quote escaping.

TextBlockTest.java
import com.github.timtebeek.books.Bundle;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class TextBlockTest {
@Test
void summary() {
String summary = new Bundle().summary();
assertEquals("Books:\n" +
"Effective Java by Joshua Bloch (2001)\n" +
"Java Concurrency in Practice by Brian Goetz (2006)\n" +
"Clean Code by Robert C. Martin (2008)\n" +
"Authors:\n" +
"Joshua Bloch\n" +
"Brian Goetz\n" +
"Robert C. Martin\n" +
"Total books: 3\n" +
"Total authors: 3\n",
summary);
}
}
warning

String concatenation with + is verbose and error-prone. Missing newlines, extra spaces, and quote escaping make it hard to see the actual content.

Even better with AssertJ

While text blocks improve readability, AssertJ can make multi-line string comparisons even clearer with better diff output.

TextBlockTest.java
import com.github.timtebeek.books.Bundle;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class TextBlockTest {
@Test
void summary() {
String summary = new Bundle().summary();
assertEquals("""
Books:
Effective Java by Joshua Bloch (2001)
Java Concurrency in Practice by Brian Goetz (2006)
Clean Code by Robert C. Martin (2008)
Authors:
Joshua Bloch
Brian Goetz
Robert C. Martin
Total books: 3
Total authors: 3
""",
summary);
}
}
info

JUnit's assertEquals() works with text blocks, but the diff output for multi-line strings can be hard to read when tests fail.

Automated migration

The OpenRewrite UpgradeToJava21 recipe can automatically convert string concatenation to text blocks in your Java code, as well as upgrade other parts of your application to align with Java 21 best practices, like using getFirst() and getLast() on sequenced collections.

In this case we're going to look at upgrading just the tests to Java 21; not yet upgrading the main source code. This split in the Java version used for src/main and src/test is possible with both Maven and Gradle. With Maven, you can set the maven.compiler.testRelease property to 21 in the maven-compiler-plugin configuration.

We find starting out with newer Java versions in tests only is often a good way to start adoption. Developers can start writing new tests and updating existing tests using the latest Java features, while the main application code can be upgraded at a more leisurely pace as it continues to target the version you're on. You'll be able to adapt your build pipelines already, and prove to management that the newer Java version work well for your team.

We will first create a custom recipe file in the root of your project, that applies the UpgradeToJava21 recipe only to test code, by using a dedicated precondition that matches test code only.

rewrite.yml
---
type: specs.openrewrite.org/v1beta/recipe
name: com.github.timtebeek.Java21ForTests
displayName: Adopt Java 21 for tests
description: Upgrade your tests to Java 21.
preconditions:
- org.openrewrite.java.search.IsLikelyTest
recipeList:
- org.openrewrite.java.migrate.UpgradeToJava21

You can run OpenRewrite recipes directly from IntelliJ IDEA Ultimate; after adding the file to your repository, you should see a run icon in the left margin offering to run the recipe.

If you're not using IntelliJ IDEA Ultimate, you can run the above recipe using one of the following methods.

The Moderne CLI allows you to run OpenRewrite recipes on your project without needing to modify your build files, against serialized Lossless Semantic Tree (LST) of your project for a considerable performance boost & across projects.

You will need to have configured the Moderne CLI on your machine before you can run the following command.

  1. If project serialized Lossless Semantic Tree is not yet available locally, then build the LST. This is only needed the first time, or after extensive changes:
shell
mod build ~/workspace/
  1. If the recipe is not available locally yet, then you can install it once using:
shell
mod config recipes jar install org.openrewrite.recipe:rewrite-migrate-java:LATEST
  1. Run the recipe.
shell
mod run ~/workspace/ --recipe com.github.timtebeek.Java21ForTests