Blocs de texte et nouvelle API String (Java 15+)
1. Présentation générale
Avant Java 15, écrire un bloc JSON, SQL ou HTML multi-lignes dans du code Java était plutôt fastidieux. Vous deviez concaténer des chaînes avec +, ajouter des \n et échapper chaque guillemet ". Les blocs de texte (officiels depuis Java 15, JEP 378) résolvent ce problème. Ils s’accompagnent de quelques nouvelles méthodes dans String.
Après avoir lu cet article, vous pourrez écrire des blocs de texte avec la bonne indentation et utiliser les échappements \ et \s. Vous saurez aussi quand utiliser formatted(), stripIndent(), translateEscapes() ainsi que certaines API supplémentaires jusqu’à Java 21.
2. Préparation
Tous les exemples s’exécutent sur Java 21. Les tests utilisent JUnit 5 et AssertJ :
<properties>
<maven.compiler.release>21</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
</dependencies>
Si vous utilisez Spring Boot 3.x, spring-boot-starter-test inclut déjà ces deux bibliothèques.
3. Les blocs de texte en détail
3.1. Syntaxe
Un bloc de texte s’ouvre avec trois guillemets doubles """ suivis immédiatement d’un retour à la ligne. Il se termine par """ :
public class TextBlockDemo {
public static void main(String[] args) {
String json = """
{
"id": 42,
"customer": "Lan",
"status": "PAID"
}
""";
System.out.print(json);
}
}
Résultat :
{
"id": 42,
"customer": "Lan",
"status": "PAID"
}
Ce code présente plusieurs points remarquables :
- Les guillemets
"à l’intérieur n’ont pas besoin d’être échappés. - L’indentation commune au début de chaque ligne est supprimée. Cela s’appelle l’espace accidentel. L’indentation relative, comme les 2 espaces avant
"id", est conservée. - Puisque
"""de fermeture est sur sa propre ligne, la chaîne se termine par\n.
Un bloc de texte est une String ordinaire. Il est entièrement traité à la compilation, donc aucun coût supplémentaire à l’exécution.
Remarque : Vous ne pouvez pas écrire le contenu immédiatement après l’ouverture
""". La ligneString s = """Hello""";provoquera une erreur de compilation : illegal text block open delimiter sequence.
3.2. La position de la fermeture """ détermine l’indentation
Le compilateur trouve la ligne avec le moins d’espaces de début, y compris la ligne contenant la fermeture """. Il supprime ensuite exactement ce nombre d’espaces du début de chaque ligne. Vous pouvez ainsi ajouter délibérément de l’indentation en tirant la fermeture """ vers la gauche :
String html = """
<ul>
<li>Order #1</li>
</ul>
""";
System.out.print(html);
<ul>
<li>Order #1</li>
</ul>
La fermeture """ est indentée de 4 colonnes moins que le contenu, donc chaque ligne garde 4 espaces au début.
Si vous ne voulez pas de caractère \n à la fin de la chaîne, placez """ immédiatement après le contenu :
String greeting = """
Xin chào Lan""";
System.out.println("[" + greeting + "]");
[Xin chào Lan]
3.3. Espaces de fin de ligne et caractères de retour à la ligne
Il y a deux autres règles à connaître :
- Les espaces à la fin de chaque ligne sont toujours supprimés.
- Tous les types de retour à la ligne (
\r\n,\r) sont normalisés en\n. Cela s’applique même si le fichier source est enregistré avec les fins de ligne Windows.
La règle 2 assure la cohérence entre les systèmes d’exploitation. Mais si votre protocole exige \r\n, par exemple pour les en-têtes HTTP ou les fichiers CSV selon RFC 4180, vous devez remplacer vous-même les caractères de retour à la ligne.
4. Deux nouvelles séquences d’échappement : \<retour à la ligne> et \s
4.1. Joindre des lignes avec \
Une longue instruction SQL est généralement écrite sur plusieurs lignes pour la lisibilité. Mais parfois, vous avez besoin qu’elle tienne sur une seule ligne, par exemple lors de la journalisation. Le backslash \ à la fin de la ligne supprime le caractère de retour à la ligne :
String sql = """
SELECT id, total \
FROM orders \
WHERE status = 'PAID'""";
System.out.println(sql);
SELECT id, total FROM orders WHERE status = 'PAID'
L’espace avant \ est conservé. C’est parce qu’il n’est plus en fin de ligne : le backslash \ se trouve après lui.
4.2. Préserver les espaces avec \s
\s équivaut à un espace (U+0020). Vous l’utilisez pour préserver les espaces de fin de ligne qui seraient normalement supprimés, par exemple lors de la création de données à largeur fixe :
String report = """
Lan \s
Minh \s
""";
report.lines().forEach(line -> System.out.println("[" + line + "]"));
[Lan ]
[Minh ]
Chaque ligne fait exactement 8 caractères. Les quatre espaces avant \s sont conservés car ils ne sont plus en fin de ligne.
4.3. Quand le contenu contient """
Deux guillemets " consécutifs vont bien. Trois consécutifs seront interprétés comme la fin du bloc de texte, donc vous devez en échapper au moins un :
String doc = """
Python docstring utilise \"\"\" pour ouvrir et fermer.
""";
5. Nouvelles API String qui accompagnent les blocs de texte
Java 15 ajoute trois méthodes à String pour supporter les blocs de texte. Ce sont aussi les étapes que le compilateur effectue lors du traitement d’un bloc de texte.
5.1. formatted()
formatted(Object... args) fonctionne comme String.format(this, args), mais vous pouvez l’appeler directement sur la chaîne. Ce style est particulièrement adapté aux blocs de texte :
record Order(long id, String customer, double total) {}
Order order = new Order(42, "Lan", 199_000);
String message = """
Commande #%d
Client : %s
Montant total : %,.0f VND
""".formatted(order.id(), order.customer(), order.total());
System.out.print(message);
Commande #42
Client : Lan
Montant total : 199 000 VND
Le séparateur des milliers dépend de la Locale par défaut de la JVM. Si vous avez besoin d’un résultat prévisible, utilisez String.format(Locale.US, ...), car formatted() n’accepte pas de paramètre Locale.
5.2. stripIndent()
Cette méthode applique l’algorithme exact d’élimination de l’espace accidentel des blocs de texte à n’importe quelle chaîne. C’est utile quand une chaîne multi-lignes provient d’un fichier ou d’une base de données plutôt que d’être dans le code source :
String raw = " SELECT *\n FROM customers\n ";
System.out.print(raw.stripIndent());
SELECT *
FROM customers
La dernière ligne ne contient que des espaces, donc elle est prise en compte pour déterminer l’indentation, puis devient une chaîne vide. C’est exactement le rôle de la fermeture """ dans un bloc de texte.
5.3. translateEscapes()
Cette méthode convertit les séquences d’échappement sous forme de texte en véritables caractères. Vous en aurez besoin quand vous lisez des modèles depuis un fichier de configuration contenant des chaînes \n ou \t :
String fromConfig = "Bonjour\\tLan\\nMerci d'avoir commandé";
System.out.println(fromConfig.translateEscapes());
Bonjour Lan
Merci d'avoir commandé
L’échappement de syntaxe incorrecte, par exemple \q, fera lever une IllegalArgumentException par la méthode. Ne l’appelez pas sur les données entrées par l’utilisateur sans capturer l’exception. Note supplémentaire : cette méthode ne traite pas \u car les échappements Unicode ont déjà été traduits par le compilateur.
6. Autres méthodes utiles (Java 11 – 21)
De nombreuses petites méthodes ont été ajoutées au fil des versions. Elles s’utilisent souvent avec les blocs de texte :
String text = """
Lan
\s
Minh
""";
// Java 11
System.out.println(" ".isBlank()); // true
System.out.println(text.lines().filter(l -> !l.isBlank()).count()); // 2
System.out.println("\u2003Lan\u2003".strip()); // "Lan" (strip comprend Unicode, trim ne le fait pas)
System.out.println("-".repeat(10)); // ----------
// Java 12
System.out.print("Lan\nMinh".indent(2)); // ajoute 2 espaces par ligne, se termine par \n
int length = "Lan".transform(String::length); // 3
// Java 21
System.out.println("order-42-paid".indexOf('-', 6, 13)); // 8, recherche uniquement dans [6, 13)
System.out.println(Arrays.toString("a,b;c".splitWithDelimiters("[,;]", 0)));
// [a, , , b, ;, c]
Quelques détails à préciser :
indent(n)ajoute n espaces au début de chaque ligne sin > 0, ou supprime au maximum n espaces sin < 0. Elle normalise toujours les retours à la ligne et ajoute\nà la fin.transform(Function)vous permet de chaîner les étapes de transformation de chaîne naturellement, par exempletemplate.transform(this::render).transform(String::strip).- À partir de Java 21,
StringBuildera aussirepeat(...):new StringBuilder().repeat("=", 5)produit=====.
7. Utiliser les blocs de texte dans Spring Boot
Dans une application Spring Boot 3.x, les blocs de texte s’adaptent parfaitement à @Query de Spring Data JPA. Vous conservez votre requête multi-lignes sans avoir à concaténer les chaînes :
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("""
SELECT o FROM Order o
WHERE o.customer.email = :email
AND o.status = 'PAID'
ORDER BY o.createdAt DESC
""")
List<Order> findPaidOrders(@Param("email") String email);
}
Les blocs de texte sont des constantes compile-time, donc ils peuvent être utilisés comme valeur d’annotation. Le paramètre est toujours lié via :email, sûr contre les injections SQL.
8. Écrire des tests pour le comportement des blocs de texte
Les règles d’indentation et de retour à la ligne peuvent être confuses. C’est pourquoi vous devriez écrire des tests pour clarifier le comportement attendu :
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class TextBlockTest {
@Test
void givenClosingDelimiterOnOwnLine_thenEndsWithNewline() {
String json = """
{
"id": 42
}
""";
assertThat(json).isEqualTo("{\n \"id\": 42\n}\n");
}
@Test
void givenBackslashAtLineEnd_thenLinesAreJoined() {
String sql = """
SELECT id \
FROM orders""";
assertThat(sql).isEqualTo("SELECT id FROM orders");
}
@Test
void givenSpaceEscape_thenTrailingSpacesArePreserved() {
String padded = """
Lan \s""";
assertThat(padded).hasSize(6).endsWith(" ");
}
@Test
void givenTemplate_whenFormatted_thenPlaceholdersAreReplaced() {
String message = """
Bonjour %s, commande #%d payée.""".formatted("Lan", 42);
assertThat(message).isEqualTo("Bonjour Lan, commande #42 payée.");
}
}
Les quatre tests passent. Le premier test est le plus important à retenir : il vous rappelle que """ de fermeture sur sa propre ligne crée un \n à la fin de la chaîne.
9. Erreurs courantes et quand ne pas les utiliser
Mélanger les tabulations et les espaces. Lors du calcul de l’espace accidentel, le compilateur traite une tabulation comme un caractère d’espace, tout comme un espace. Si les lignes sont indentées avec deux styles différents, le résultat sera décalé même s’il semble aligné dans l’IDE. Configurez votre éditeur pour utiliser uniquement des espaces.
Oublier le \n à la fin. Lors de la comparaison de chaînes ou de la concaténation d’un bloc de texte avec une autre chaîne, le \n supplémentaire cause souvent des erreurs du type “c’est égal à l’oeil mais equals dit false”. Si vous n’en avez pas besoin, placez """ immédiatement après le contenu, ou appelez strip().
Utiliser formatted() pour construire SQL avec des données utilisateur. Les blocs de texte rendent seulement le code plus lisible, pas plus sûr.
// FAUX : vulnérabilité SQL injection
String sql = """
SELECT * FROM customers WHERE email = '%s'
""".formatted(email);
Utilisez PreparedStatement, JdbcTemplate avec le paramètre ?, ou @Query avec des paramètres nommés.
Remarque : Avec JSON, les blocs de texte conviennent aux données de test ou aux charges utiles fixes. Si la charge utile dépend de données dynamiques, utilisez Jackson pour sérialiser l’objet. La concaténation manuelle n’échappera pas les caractères spéciaux comme
"ou\dans le nom du client.
Chaînes courtes d’une ligne. "Lan" reste meilleur qu’un bloc de texte de trois lignes. Utilisez les blocs de texte seulement quand le contenu a réellement plusieurs lignes ou contient beaucoup de guillemets.
Modèles trop gros. Un email HTML de plusieurs centaines de lignes dans une classe Java rend le code difficile à maintenir. Dans ce cas, vous devez le déplacer vers un fichier de modèle, par exemple en utilisant Thymeleaf ou une ressource dans src/main/resources.
10. Conclusion
Les blocs de texte vous permettent d’écrire JSON, SQL, HTML en Java de manière concise et aussi lisible que le format d’origine. Vous devez juste vous souvenir de quelques règles :
- L’ouverture
"""doit être suivie immédiatement d’un retour à la ligne. - La position de la fermeture
"""détermine l’indentation et si la chaîne se termine par\nou non. - Les espaces de fin de ligne sont supprimés. Les retours à la ligne sont toujours normalisés en
\n. - Utilisez
\pour joindre les lignes et\spour préserver les espaces.
Concernant l’API, formatted() est excellent pour remplir les valeurs dans un bloc de texte. stripIndent() et translateEscapes() vous permettent d’appliquer la même logique aux chaînes lues de l’extérieur. Des méthodes comme lines(), strip(), indent(), transform() et splitWithDelimiters() de Java 21 vous aident à traiter les chaînes multi-lignes sans bibliothèque supplémentaire. Enfin, écrivez des tests pour clarifier le comportement souhaité, et n’utilisez pas les blocs de texte pour construire manuellement SQL avec les données de l’utilisateur.