Блок текста (text block) в Java — это многострочный строковый литерал в тройных кавычках """. Компилятор превращает его в обычный объект String: приводит переводы строк к \n, убирает общий отступ, который нужен только для красоты кода, и затем обрабатывает escape-последовательности. Текстовые блоки стали стандартной частью языка в Java 15 (JEP 378), в Java 13 и 14 они были preview-функцией.
Содержание
- Три термина, которые легко спутать
- Минимальный пример
- Правила синтаксиса
- Как компилятор обрабатывает блок
- Escape-последовательности в текстовом блоке
- Подстановка значений: formatted() и склейка
- Чем блок текста отличается от обычной строки
- Если не получилось
- Выводы
- Где применяется / связь с практикой
- FAQ
Словом «блок текста» называют и другие вещи: в HTML это блочные элементы вроде <p> и <div>, в интернет-рекламе — текстовое объявление без картинок. Здесь речь только о Java: синтаксис, правила отступов, экранирование, formatted() и чем блок отличается от обычной строки. Все примеры проверены на OpenJDK 25.0.4 (Java 25 LTS).
Три термина, которые легко спутать
- Строковый литерал — запись значения в исходном коде:
"abc"или блок в""". - Строка — объект
Stringво время работы программы. И обычный литерал, и текстовый блок дают один и тот же тип, разница есть только в исходнике. - Случайный и существенный отступ. Случайный (incidental) — пробелы, которые появились из-за вложенности кода, компилятор их убирает. Существенный (essential) — пробелы, которые вы хотите видеть в строке, они остаются.
Минимальный пример
public class Main {
public static void main(String[] args) {
String json = """
{
"name": "Otus",
"year": 2026
}
""";
System.out.print(json);
System.out.println("строк: " + json.lines().count());
System.out.println("в конце \\n: " + json.endsWith("\n"));
}
}
Запуск без отдельной компиляции: java Main.java. Вывод:
{
"name": "Otus",
"year": 2026
}
строк: 4
в конце \n: true
Что здесь видно. Кавычки внутри JSON не нужно экранировать, как в "{\"name\": \"Otus\"}". Двенадцать пробелов слева, которые стоят в коде, в строку не попали, а два пробела перед "name" сохранились — это существенный отступ относительно самой левой строки. Строка заканчивается переводом строки, потому что закрывающие """ стоят на отдельной строке.
Правила синтаксиса
Открывающие """ должны заканчивать строку исходника: после них допустимы только пробелы и перевод строки. Содержимое начинается со следующей строки. Попытка записать блок в одну строку не компилируется:
public class Bad {
public static void main(String[] args) {
String s = """Привет""";
System.out.println(s);
}
}
Результат javac Bad.java:
Bad.java:3: error: illegal text block open delimiter sequence, missing line terminator
String s = """Привет""";
^
Bad.java:3: error: illegal text block open delimiter sequence, missing line terminator
String s = """Привет""";
^
2 errors
Ошибка выдается дважды: вторые """ компилятор тоже читает как открывающие.
Исправление — перенести содержимое на следующую строку. Для короткого однострочного текста блок не нужен, обычный литерал "Привет" проще.
Закрывающие """ можно ставить где угодно: на отдельной строке или сразу после последнего символа. От этого зависят две вещи — будет ли в конце \n и какой отступ сохранится.
Как компилятор обрабатывает блок
Порядок фиксирован, и он объясняет почти все сюрпризы:
- Переводы строк. Окончания
\r\n(Windows) и\rприводятся к\n. Строка получается одинаковой независимо от того, в какой ОС сохранен исходник. - Случайный отступ. Компилятор находит минимальный отступ среди непустых строк содержимого, а также строки с закрывающими
""", если они стоят на отдельной строке. Этот отступ срезается у всех строк. Пробелы в конце каждой строки тоже удаляются. - Escape-последовательности. Только после этого обрабатываются
\n,\t,\",\sи остальные. Поэтому\sв конце строки переживает шаг 2: на момент удаления хвостовых пробелов это еще не пробел, а два символа.
Где ставить закрывающие кавычки
Позиция закрывающих """ — главный рычаг управления отступом. Три варианта в одной программе:
public class Indent {
static void show(String title, String s) {
System.out.println(title);
s.lines().forEach(line -> System.out.println("|" + line + "|"));
}
public static void main(String[] args) {
String a = """
SELECT id, name
FROM users
""";
String b = """
SELECT id, name
FROM users
""";
String c = """
SELECT id, name
FROM users""";
show("A: закрывающие кавычки под текстом", a);
show("B: закрывающие кавычки левее на 4", b);
show("C: кавычки в конце последней строки", c);
System.out.println("C заканчивается переводом строки: " + c.endsWith("\n"));
}
}
A: закрывающие кавычки под текстом
|SELECT id, name|
|FROM users|
B: закрывающие кавычки левее на 4
| SELECT id, name|
| FROM users|
C: кавычки в конце последней строки
|SELECT id, name|
|FROM users|
C заканчивается переводом строки: false
Где стоят """ |
Отступ в строке | \n в конце |
|---|---|---|
| На своей строке, ровно под текстом | Нет | Есть |
| На своей строке, левее текста | Сохраняется разница в пробелах | Есть |
| Сразу после последнего символа | Нет | Нет |
Табы и пробелы
Табуляция и пробел для компилятора — по одному символу отступа, ширину таба в редакторе он не учитывает. Если одна строка отбита табом, а другая пробелами, результат будет неожиданным. Флаг javac -Xlint:text-blocks предупреждает об этом:
warning: [text-blocks] inconsistent white space indentation
Практическое правило: в текстовых блоках использовать только пробелы и включить в IDE показ невидимых символов.
Escape-последовательности в текстовом блоке
Блок текста — не «сырая» строка: обратный слэш в нем работает так же, как в обычном литерале. Кроме привычных \n, \t, \\, \" есть две последовательности, которые появились вместе с текстовыми блоками (Java 14 preview, стандарт с Java 15):
| Последовательность | Что делает | Где работает |
|---|---|---|
\<перевод строки> |
Склеивает строку исходника со следующей, \n не добавляется |
Только в текстовом блоке |
\s |
Один пробел (U+0020), не удаляется как хвостовой | В блоке и в обычной строке |
\""" |
Тройные кавычки внутри блока | В блоке |
Пример со всеми тремя:
public class Escapes {
public static void main(String[] args) {
String quote = """
Тройные кавычки внутри: \"""
Одиночные можно без экранирования: "text"
""";
String oneLine = """
Очень длинная строка, \
которую удобно переносить в коде
""";
String padded = """
red \s
green\s
""";
System.out.print(quote);
System.out.print(oneLine);
padded.lines().forEach(l -> System.out.println("[" + l + "] длина " + l.length()));
}
}
Тройные кавычки внутри: """
Одиночные можно без экранирования: "text"
Очень длинная строка, которую удобно переносить в коде
[red ] длина 6
[green ] длина 6
Пробелы перед \s в строке red \s сохранились, потому что после них стоит не пробел, а последовательность. Так выравнивают колонки фиксированной ширины.
Ошибка: тройные кавычки без экранирования
public class Bad2 {
public static void main(String[] args) {
String s = """
Он сказал: """Привет"""
""";
System.out.println(s);
}
}
Bad2.java:4: error: ';' expected
Он сказал: """Привет"""
^
Bad2.java:4: error: ';' expected
Он сказал: """Привет"""
^
2 errors
Первые же """ внутри содержимого компилятор считает закрывающими. Исправление: экранировать хотя бы первую кавычку каждой тройки — \"""Привет\""".
Регулярные выражения
Раз слэш не «сырой», в регулярном выражении его все так же надо удваивать:
public class Regex {
public static void main(String[] args) {
String pattern = """
\\d{3}-\\d{2}""";
System.out.println(pattern);
System.out.println("123-45".matches(pattern));
}
}
\d{3}-\d{2}
true
Выигрыш от блока здесь только в том, что не нужно экранировать кавычки. Для коротких регулярок обычная строка не хуже.
Подстановка значений: formatted() и склейка
Интерполяции вида ${name} в Java нет. Строковые шаблоны (String Templates) были preview в Java 21 и 22 и затем убраны из языка, в Java 25 их нет. Значения подставляют методом formatted() — это String.format, вызванный у самой строки:
public class Fmt {
public static void main(String[] args) {
String template = """
Курс: %s
Модулей: %d
""";
System.out.print(template.formatted("Java", 5));
String raw = " a\n b\n c";
System.out.println(raw.stripIndent());
String escaped = "Колонка1\\tКолонка2";
System.out.println(escaped);
System.out.println(escaped.translateEscapes());
}
}
Курс: Java
Модулей: 5
a
b
c
Колонка1\tКолонка2
Колонка1 Колонка2
Вместе с текстовыми блоками в Java 15 появились три метода String. formatted(Object... args) — форматирование по шаблону. stripIndent() — то же удаление случайного отступа, что делает компилятор, но для строки, полученной во время работы (например, прочитанной из файла). translateEscapes() — превращает текст \t из двух символов в настоящую табуляцию.
Ловушка при склейке
Хвостовые пробелы удаляются и у последней строки, если закрывающие """ стоят сразу за ней. Код ниже выглядит правильно, но пробел после запятой теряется:
String name = "Otus";
String mixed = """
Привет, """ + name + "!";
System.out.println(mixed); // Привет,Otus!
Исправление — \s перед кавычками или шаблон с formatted():
String fixed1 = """
Привет,\s""" + name + "!";
String fixed2 = """
Привет, %s!""".formatted(name);
System.out.println(fixed1); // Привет, Otus!
System.out.println(fixed2); // Привет, Otus!
SQL в текстовом блоке и инъекции
Текстовые блоки часто берут для SQL, и formatted() соблазняет подставить значение прямо в запрос. Так делать нельзя: при вводе x' OR '1'='1 получится WHERE login = 'x' OR '1'='1', то есть условие, истинное для всех строк. Текстовый блок хранит только текст запроса, а значения передаются через параметры ? и PreparedStatement.setString():
String sql = """
SELECT id, name FROM users
WHERE login = ?
""";
То же касается HTML: блок с разметкой удобен для шаблона, но данные пользователя в него вставляют только после экранирования.
Чем блок текста отличается от обычной строки
| Свойство | Обычный литерал "..." |
Текстовый блок """...""" |
|---|---|---|
| Тип результата | String |
String |
| Несколько строк | Через \n и + |
Как написано в коде |
Кавычки " внутри |
Нужно \" |
Можно без экранирования |
| Отступ кода | Попадает в строку, если набран | Общий отступ срезается |
| Хвостовые пробелы | Сохраняются | Удаляются (кроме \s) |
| Интернирование | Да, как константа | Да, как константа |
| Минимальная версия | Любая | Java 15 (13-14 — preview) |
Проверка на практике:
public class Same {
public static void main(String[] args) {
String block = """
Hello
World""";
String classic = "Hello\nWorld";
System.out.println(block.equals(classic));
System.out.println(block == classic);
System.out.println(block.getClass().getName());
}
}
true
true
java.lang.String
== вернул true только потому, что обе строки — одинаковые константы времени компиляции, и JVM хранит их в одном экземпляре. Для строк, собранных во время работы, это не так, поэтому сравнивают всегда через equals().
Если не получилось
illegal text block open delimiter sequence— после открывающих"""есть текст. Перенести его на следующую строку.';' expectedвнутри блока — в содержимом встретились""". Экранировать:\""".- Лишние пробелы слева — закрывающие
"""стоят левее текста или в строках смешаны табы и пробелы. Выровнять кавычки и проверить-Xlint:text-blocks. - В конце лишний перевод строки — поставить
"""сразу после последнего символа или добавить\в конце последней строки. - Пропал пробел в конце строки — заменить его на
\s. - Код не компилируется на старом JDK — текстовые блоки требуют Java 15+, проверить
java -versionи настройкуreleaseв Maven или Gradle.
Выводы
- Блок текста в Java — многострочный литерал в
""", результат — обычныйString; стандарт с Java 15. - Компилятор выполняет три шага по порядку: нормализует переводы строк, срезает общий отступ и хвостовые пробелы, затем обрабатывает escape-последовательности.
- Отступом и финальным
\nуправляет позиция закрывающих""". \<перевод строки>склеивает строки,\sсохраняет пробел,\"""вставляет тройные кавычки; обратный слэш не «сырой».- Значения подставляются через
formatted(), но в SQL — только параметрамиPreparedStatement.
Где применяется / связь с практикой
Освойте тему на практике
Текстовые блоки встречаются там, где в Java-коде живут фрагменты другого языка: SQL-запросы в репозиториях, JSON в тестах и моках, HTML-шаблоны писем, конфигурации. Чтобы уверенно работать со строками, форматированием и JDBC, нужна база языка, которую системно дает курс «Java разработчик. Базовый уровень». Попробовать формат обучения можно на открытых уроках Otus.
FAQ
Можно ли использовать текстовый блок как аргумент метода или в switch?
Да. Это обычная константа типа String, ее можно передать в метод, объявить static final и использовать как метку case.
Работают ли текстовые блоки в Kotlin или Android-проекте?
В Kotlin есть свои «сырые» строки в """ с методом trimIndent(), это другой механизм. В Java-коде под Android текстовые блоки доступны, если сборка настроена на Java 15+ и инструменты сборки поддерживают эту версию языка.
Попадает ли отступ в байт-код?
Нет. Все три шага выполняет компилятор, в class-файл записывается уже готовая строка, поэтому затрат во время работы программы нет.



