[{"data":1,"prerenderedAt":1538},["ShallowReactive",2],{"blog-\u002Fblog\u002Fjava-text-blocks":3},{"id":4,"title":5,"body":6,"description":1525,"difficulty":1526,"extension":1527,"framework":1397,"frameworkSlug":23,"meta":1528,"navigation":130,"order":97,"path":1529,"qaPath":1530,"seo":1531,"stem":1532,"subtopic":1533,"topic":1534,"topicSlug":1535,"updated":1536,"__hash__":1537},"blog\u002Fblog\u002Fjava-text-blocks.md","Java Text Blocks — Multi-line Strings Without the Escape Clutter",{"type":7,"value":8,"toc":1502},"minimark",[9,14,18,169,180,257,260,264,281,355,362,366,373,432,438,446,453,494,498,504,510,521,568,572,575,583,586,621,628,634,681,685,696,790,793,797,801,854,857,861,919,925,929,994,998,1107,1110,1114,1121,1151,1161,1199,1203,1210,1218,1258,1270,1328,1336,1381,1385,1451,1454,1458,1498],[10,11,13],"h2",{"id":12},"the-problem-text-blocks-solve","The problem text blocks solve",[15,16,17],"p",{},"Writing multi-line strings in Java used to require either concatenation or explicit escape\nsequences — neither of which lets you see what the string actually looks like:",[19,20,25],"pre",{"className":21,"code":22,"language":23,"meta":24,"style":24},"language-java shiki shiki-themes github-light github-dark","\u002F\u002F Pre-Java-15: noisy, error-prone, hard to diff\nString json = \"{\\n\" +\n              \"  \\\"name\\\": \\\"Alice\\\",\\n\" +\n              \"  \\\"age\\\": 30\\n\" +\n              \"}\";\n\nString sql = \"SELECT u.id, u.name\\n\" +\n             \"FROM users u\\n\" +\n             \"WHERE u.active = true\";\n","java","",[26,27,28,37,62,95,116,125,132,149,161],"code",{"__ignoreMap":24},[29,30,33],"span",{"class":31,"line":32},"line",1,[29,34,36],{"class":35},"sJ8bj","\u002F\u002F Pre-Java-15: noisy, error-prone, hard to diff\n",[29,38,40,44,48,52,56,59],{"class":31,"line":39},2,[29,41,43],{"class":42},"sVt8B","String json ",[29,45,47],{"class":46},"szBVR","=",[29,49,51],{"class":50},"sZZnC"," \"{",[29,53,55],{"class":54},"sj4cs","\\n",[29,57,58],{"class":50},"\"",[29,60,61],{"class":46}," +\n",[29,63,65,68,71,74,76,79,81,84,86,89,91,93],{"class":31,"line":64},3,[29,66,67],{"class":50},"              \"  ",[29,69,70],{"class":54},"\\\"",[29,72,73],{"class":50},"name",[29,75,70],{"class":54},[29,77,78],{"class":50},": ",[29,80,70],{"class":54},[29,82,83],{"class":50},"Alice",[29,85,70],{"class":54},[29,87,88],{"class":50},",",[29,90,55],{"class":54},[29,92,58],{"class":50},[29,94,61],{"class":46},[29,96,98,100,102,105,107,110,112,114],{"class":31,"line":97},4,[29,99,67],{"class":50},[29,101,70],{"class":54},[29,103,104],{"class":50},"age",[29,106,70],{"class":54},[29,108,109],{"class":50},": 30",[29,111,55],{"class":54},[29,113,58],{"class":50},[29,115,61],{"class":46},[29,117,119,122],{"class":31,"line":118},5,[29,120,121],{"class":50},"              \"}\"",[29,123,124],{"class":42},";\n",[29,126,128],{"class":31,"line":127},6,[29,129,131],{"emptyLinePlaceholder":130},true,"\n",[29,133,135,138,140,143,145,147],{"class":31,"line":134},7,[29,136,137],{"class":42},"String sql ",[29,139,47],{"class":46},[29,141,142],{"class":50}," \"SELECT u.id, u.name",[29,144,55],{"class":54},[29,146,58],{"class":50},[29,148,61],{"class":46},[29,150,152,155,157,159],{"class":31,"line":151},8,[29,153,154],{"class":50},"             \"FROM users u",[29,156,55],{"class":54},[29,158,58],{"class":50},[29,160,61],{"class":46},[29,162,164,167],{"class":31,"line":163},9,[29,165,166],{"class":50},"             \"WHERE u.active = true\"",[29,168,124],{"class":42},[15,170,171,175,176,179],{},[172,173,174],"strong",{},"Text blocks"," (finalized in ",[172,177,178],{},"Java 15",", JEP 378) replace this with a multi-line\nliteral that looks like its content:",[19,181,183],{"className":21,"code":182,"language":23,"meta":24,"style":24},"String json = \"\"\"\n        {\n          \"name\": \"Alice\",\n          \"age\": 30\n        }\n        \"\"\";\n\nString sql = \"\"\"\n        SELECT u.id, u.name\n        FROM users u\n        WHERE u.active = true\n        \"\"\";\n",[26,184,185,194,199,204,209,214,221,225,233,238,244,250],{"__ignoreMap":24},[29,186,187,189,191],{"class":31,"line":32},[29,188,43],{"class":42},[29,190,47],{"class":46},[29,192,193],{"class":50}," \"\"\"\n",[29,195,196],{"class":31,"line":39},[29,197,198],{"class":50},"        {\n",[29,200,201],{"class":31,"line":64},[29,202,203],{"class":50},"          \"name\": \"Alice\",\n",[29,205,206],{"class":31,"line":97},[29,207,208],{"class":50},"          \"age\": 30\n",[29,210,211],{"class":31,"line":118},[29,212,213],{"class":50},"        }\n",[29,215,216,219],{"class":31,"line":127},[29,217,218],{"class":50},"        \"\"\"",[29,220,124],{"class":42},[29,222,223],{"class":31,"line":134},[29,224,131],{"emptyLinePlaceholder":130},[29,226,227,229,231],{"class":31,"line":151},[29,228,137],{"class":42},[29,230,47],{"class":46},[29,232,193],{"class":50},[29,234,235],{"class":31,"line":163},[29,236,237],{"class":50},"        SELECT u.id, u.name\n",[29,239,241],{"class":31,"line":240},10,[29,242,243],{"class":50},"        FROM users u\n",[29,245,247],{"class":31,"line":246},11,[29,248,249],{"class":50},"        WHERE u.active = true\n",[29,251,253,255],{"class":31,"line":252},12,[29,254,218],{"class":50},[29,256,124],{"class":42},[15,258,259],{},"No escape sequences, no concatenation, no visual noise. The string is exactly what you\nsee.",[10,261,263],{"id":262},"syntax-opening-and-closing-delimiters","Syntax — opening and closing delimiters",[15,265,266,267,270,271,274,275,277,278,280],{},"A text block opens with ",[26,268,269],{},"\"\"\""," followed by a ",[172,272,273],{},"mandatory newline"," — content cannot start\non the same line as ",[26,276,269],{},". It closes with ",[26,279,269],{}," either on its own line or at the end of\nthe last content line:",[19,282,284],{"className":21,"code":283,"language":23,"meta":24,"style":24},"\u002F\u002F Closing \"\"\" on its own line — trailing newline in result:\nString s1 = \"\"\"\n        hello\n        world\n        \"\"\";\n\u002F\u002F s1 == \"hello\\nworld\\n\"\n\n\u002F\u002F Closing \"\"\" inline — no trailing newline:\nString s2 = \"\"\"\n        hello\n        world\"\"\";\n\u002F\u002F s2 == \"hello\\nworld\"\n",[26,285,286,291,300,305,310,316,321,325,330,339,343,350],{"__ignoreMap":24},[29,287,288],{"class":31,"line":32},[29,289,290],{"class":35},"\u002F\u002F Closing \"\"\" on its own line — trailing newline in result:\n",[29,292,293,296,298],{"class":31,"line":39},[29,294,295],{"class":42},"String s1 ",[29,297,47],{"class":46},[29,299,193],{"class":50},[29,301,302],{"class":31,"line":64},[29,303,304],{"class":50},"        hello\n",[29,306,307],{"class":31,"line":97},[29,308,309],{"class":50},"        world\n",[29,311,312,314],{"class":31,"line":118},[29,313,218],{"class":50},[29,315,124],{"class":42},[29,317,318],{"class":31,"line":127},[29,319,320],{"class":35},"\u002F\u002F s1 == \"hello\\nworld\\n\"\n",[29,322,323],{"class":31,"line":134},[29,324,131],{"emptyLinePlaceholder":130},[29,326,327],{"class":31,"line":151},[29,328,329],{"class":35},"\u002F\u002F Closing \"\"\" inline — no trailing newline:\n",[29,331,332,335,337],{"class":31,"line":163},[29,333,334],{"class":42},"String s2 ",[29,336,47],{"class":46},[29,338,193],{"class":50},[29,340,341],{"class":31,"line":240},[29,342,304],{"class":50},[29,344,345,348],{"class":31,"line":246},[29,346,347],{"class":50},"        world\"\"\"",[29,349,124],{"class":42},[29,351,352],{"class":31,"line":252},[29,353,354],{"class":35},"\u002F\u002F s2 == \"hello\\nworld\"\n",[15,356,357,358,361],{},"A text block is a plain ",[26,359,360],{},"java.lang.String"," — same type, same pool, same API. The\ncompiler evaluates it at compile time; at runtime there is no performance difference from\na regular string literal.",[10,363,365],{"id":364},"incidental-whitespace-stripping","Incidental whitespace stripping",[15,367,368,369,372],{},"When you indent a text block to align with its surrounding code, the compiler strips the\n",[172,370,371],{},"common leading whitespace"," from every content line:",[19,374,376],{"className":21,"code":375,"language":23,"meta":24,"style":24},"class Config {\n    static final String QUERY = \"\"\"\n            SELECT *\n            FROM orders\n            WHERE status = 'OPEN'\n            \"\"\";\n}\n",[26,377,378,390,405,410,415,420,427],{"__ignoreMap":24},[29,379,380,383,387],{"class":31,"line":32},[29,381,382],{"class":46},"class",[29,384,386],{"class":385},"sScJk"," Config",[29,388,389],{"class":42}," {\n",[29,391,392,395,398,401,403],{"class":31,"line":39},[29,393,394],{"class":46},"    static",[29,396,397],{"class":46}," final",[29,399,400],{"class":42}," String QUERY ",[29,402,47],{"class":46},[29,404,193],{"class":50},[29,406,407],{"class":31,"line":64},[29,408,409],{"class":50},"            SELECT *\n",[29,411,412],{"class":31,"line":97},[29,413,414],{"class":50},"            FROM orders\n",[29,416,417],{"class":31,"line":118},[29,418,419],{"class":50},"            WHERE status = 'OPEN'\n",[29,421,422,425],{"class":31,"line":127},[29,423,424],{"class":50},"            \"\"\"",[29,426,124],{"class":42},[29,428,429],{"class":31,"line":134},[29,430,431],{"class":42},"}\n",[15,433,434,435,437],{},"Every content line above has 12 leading spaces; the closing ",[26,436,269],{}," also has 12. The\ncompiler strips 12 spaces from every line, leaving:",[19,439,444],{"className":440,"code":442,"language":443},[441],"language-text","SELECT *\nFROM orders\nWHERE status = 'OPEN'\n","text",[26,445,442],{"__ignoreMap":24},[15,447,448,449,452],{},"The algorithm is: find the minimum indentation among non-blank content lines and the\nclosing-delimiter line, then strip exactly that many spaces from each line. The closing\ndelimiter position is therefore how you ",[172,450,451],{},"control"," the output indentation:",[19,454,456],{"className":21,"code":455,"language":23,"meta":24,"style":24},"\u002F\u002F Move closing \"\"\" left to reduce stripping (keep 4 spaces of indentation):\nString indented = \"\"\"\n        outer\n            inner\n    \"\"\";\n\u002F\u002F \"    outer\\n        inner\\n\"\n",[26,457,458,463,472,477,482,489],{"__ignoreMap":24},[29,459,460],{"class":31,"line":32},[29,461,462],{"class":35},"\u002F\u002F Move closing \"\"\" left to reduce stripping (keep 4 spaces of indentation):\n",[29,464,465,468,470],{"class":31,"line":39},[29,466,467],{"class":42},"String indented ",[29,469,47],{"class":46},[29,471,193],{"class":50},[29,473,474],{"class":31,"line":64},[29,475,476],{"class":50},"        outer\n",[29,478,479],{"class":31,"line":97},[29,480,481],{"class":50},"            inner\n",[29,483,484,487],{"class":31,"line":118},[29,485,486],{"class":50},"    \"\"\"",[29,488,124],{"class":42},[29,490,491],{"class":31,"line":127},[29,492,493],{"class":35},"\u002F\u002F \"    outer\\n        inner\\n\"\n",[10,495,497],{"id":496},"line-ending-normalisation","Line-ending normalisation",[15,499,500,501,503],{},"The compiler normalises all line endings to ",[26,502,55],{}," (LF) regardless of what the source file\nuses (Windows CRLF, Unix LF, old Mac CR). Text blocks are therefore safe to use in\ncross-platform codebases without worrying about line-ending differences:",[19,505,508],{"className":506,"code":507,"language":443},[441],"Source file on Windows (CRLF) → compiler → text block string uses LF everywhere\n",[26,509,507],{"__ignoreMap":24},[15,511,512,513,516,517,520],{},"If you need ",[26,514,515],{},"\\r\\n"," for a specific protocol (HTTP headers, email, CSV), add ",[26,518,519],{},"\\r"," explicitly:",[19,522,524],{"className":21,"code":523,"language":23,"meta":24,"style":24},"String response = \"\"\"\n        HTTP\u002F1.1 200 OK\\r\n        Content-Type: application\u002Fjson\\r\n        \\r\n        {\"status\":\"ok\"}\\r\n        \"\"\";\n",[26,525,526,535,543,550,555,562],{"__ignoreMap":24},[29,527,528,531,533],{"class":31,"line":32},[29,529,530],{"class":42},"String response ",[29,532,47],{"class":46},[29,534,193],{"class":50},[29,536,537,540],{"class":31,"line":39},[29,538,539],{"class":50},"        HTTP\u002F1.1 200 OK",[29,541,542],{"class":54},"\\r\n",[29,544,545,548],{"class":31,"line":64},[29,546,547],{"class":50},"        Content-Type: application\u002Fjson",[29,549,542],{"class":54},[29,551,552],{"class":31,"line":97},[29,553,554],{"class":54},"        \\r\n",[29,556,557,560],{"class":31,"line":118},[29,558,559],{"class":50},"        {\"status\":\"ok\"}",[29,561,542],{"class":54},[29,563,564,566],{"class":31,"line":127},[29,565,218],{"class":50},[29,567,124],{"class":42},[10,569,571],{"id":570},"new-escape-sequences","New escape sequences",[15,573,574],{},"Java 15 introduced two escape sequences specifically for text blocks:",[576,577,579,580],"h3",{"id":578},"line-continuation","Line continuation: ",[26,581,582],{},"\\",[15,584,585],{},"Suppresses the newline at the end of a line, joining it visually wrapped text into one\nlogical line:",[19,587,589],{"className":21,"code":588,"language":23,"meta":24,"style":24},"String sentence = \"\"\"\n        The quick brown fox \\\n        jumps over the lazy dog.\n        \"\"\";\n\u002F\u002F \"The quick brown fox jumps over the lazy dog.\\n\"\n",[26,590,591,600,605,610,616],{"__ignoreMap":24},[29,592,593,596,598],{"class":31,"line":32},[29,594,595],{"class":42},"String sentence ",[29,597,47],{"class":46},[29,599,193],{"class":50},[29,601,602],{"class":31,"line":39},[29,603,604],{"class":50},"        The quick brown fox \\\n",[29,606,607],{"class":31,"line":64},[29,608,609],{"class":50},"        jumps over the lazy dog.\n",[29,611,612,614],{"class":31,"line":97},[29,613,218],{"class":50},[29,615,124],{"class":42},[29,617,618],{"class":31,"line":118},[29,619,620],{"class":35},"\u002F\u002F \"The quick brown fox jumps over the lazy dog.\\n\"\n",[576,622,624,625],{"id":623},"space-anchor-s","Space anchor: ",[26,626,627],{},"\\s",[15,629,630,631,633],{},"Preserves a trailing space that would otherwise be stripped. The compiler removes all\ntrailing whitespace from each line; ",[26,632,627],{}," marks the last character as a space that must\nsurvive:",[19,635,637],{"className":21,"code":636,"language":23,"meta":24,"style":24},"String table = \"\"\"\n        apple  \\s\n        banana \\s\n        cherry \\s\n        \"\"\";\n\u002F\u002F Each line ends with exactly one space\n",[26,638,639,648,656,663,670,676],{"__ignoreMap":24},[29,640,641,644,646],{"class":31,"line":32},[29,642,643],{"class":42},"String table ",[29,645,47],{"class":46},[29,647,193],{"class":50},[29,649,650,653],{"class":31,"line":39},[29,651,652],{"class":50},"        apple  ",[29,654,655],{"class":54},"\\s\n",[29,657,658,661],{"class":31,"line":64},[29,659,660],{"class":50},"        banana ",[29,662,655],{"class":54},[29,664,665,668],{"class":31,"line":97},[29,666,667],{"class":50},"        cherry ",[29,669,655],{"class":54},[29,671,672,674],{"class":31,"line":118},[29,673,218],{"class":50},[29,675,124],{"class":42},[29,677,678],{"class":31,"line":127},[29,679,680],{"class":35},"\u002F\u002F Each line ends with exactly one space\n",[10,682,684],{"id":683},"value-interpolation-with-formatted","Value interpolation with formatted()",[15,686,687,688,691,692,695],{},"Text blocks don't have template syntax, but Java 15 added ",[26,689,690],{},"String.formatted()"," — an\ninstance method equivalent of ",[26,693,694],{},"String.format()"," — which chains naturally:",[19,697,699],{"className":21,"code":698,"language":23,"meta":24,"style":24},"String name = \"Alice\";\nint score = 95;\n\nString report = \"\"\"\n        Name:  %s\n        Score: %d\n        Grade: %s\n        \"\"\".formatted(name, score, score >= 90 ? \"A\" : \"B\");\n",[26,700,701,713,728,732,741,746,751,756],{"__ignoreMap":24},[29,702,703,706,708,711],{"class":31,"line":32},[29,704,705],{"class":42},"String name ",[29,707,47],{"class":46},[29,709,710],{"class":50}," \"Alice\"",[29,712,124],{"class":42},[29,714,715,718,721,723,726],{"class":31,"line":39},[29,716,717],{"class":46},"int",[29,719,720],{"class":42}," score ",[29,722,47],{"class":46},[29,724,725],{"class":54}," 95",[29,727,124],{"class":42},[29,729,730],{"class":31,"line":64},[29,731,131],{"emptyLinePlaceholder":130},[29,733,734,737,739],{"class":31,"line":97},[29,735,736],{"class":42},"String report ",[29,738,47],{"class":46},[29,740,193],{"class":50},[29,742,743],{"class":31,"line":118},[29,744,745],{"class":50},"        Name:  %s\n",[29,747,748],{"class":31,"line":127},[29,749,750],{"class":50},"        Score: %d\n",[29,752,753],{"class":31,"line":134},[29,754,755],{"class":50},"        Grade: %s\n",[29,757,758,760,763,766,769,772,775,778,781,784,787],{"class":31,"line":151},[29,759,218],{"class":50},[29,761,762],{"class":42},".",[29,764,765],{"class":385},"formatted",[29,767,768],{"class":42},"(name, score, score ",[29,770,771],{"class":46},">=",[29,773,774],{"class":54}," 90",[29,776,777],{"class":46}," ?",[29,779,780],{"class":50}," \"A\"",[29,782,783],{"class":46}," :",[29,785,786],{"class":50}," \"B\"",[29,788,789],{"class":42},");\n",[15,791,792],{},"For simple substitution this works well. For complex templates with loops or conditionals,\nuse a dedicated template engine (Freemarker, Thymeleaf, Mustache).",[10,794,796],{"id":795},"practical-examples","Practical examples",[576,798,800],{"id":799},"sql","SQL",[19,802,804],{"className":21,"code":803,"language":23,"meta":24,"style":24},"String query = \"\"\"\n        SELECT u.id, u.name, o.total\n        FROM users u\n        JOIN orders o ON o.user_id = u.id\n        WHERE u.active = true\n          AND o.created_at > :since\n        ORDER BY o.total DESC\n        LIMIT :limit\n        \"\"\";\n",[26,805,806,815,820,824,829,833,838,843,848],{"__ignoreMap":24},[29,807,808,811,813],{"class":31,"line":32},[29,809,810],{"class":42},"String query ",[29,812,47],{"class":46},[29,814,193],{"class":50},[29,816,817],{"class":31,"line":39},[29,818,819],{"class":50},"        SELECT u.id, u.name, o.total\n",[29,821,822],{"class":31,"line":64},[29,823,243],{"class":50},[29,825,826],{"class":31,"line":97},[29,827,828],{"class":50},"        JOIN orders o ON o.user_id = u.id\n",[29,830,831],{"class":31,"line":118},[29,832,249],{"class":50},[29,834,835],{"class":31,"line":127},[29,836,837],{"class":50},"          AND o.created_at > :since\n",[29,839,840],{"class":31,"line":134},[29,841,842],{"class":50},"        ORDER BY o.total DESC\n",[29,844,845],{"class":31,"line":151},[29,846,847],{"class":50},"        LIMIT :limit\n",[29,849,850,852],{"class":31,"line":163},[29,851,218],{"class":50},[29,853,124],{"class":42},[15,855,856],{},"The query keywords align visually, making it trivial to read and diff.",[576,858,860],{"id":859},"json","JSON",[19,862,864],{"className":21,"code":863,"language":23,"meta":24,"style":24},"String body = \"\"\"\n        {\n          \"event\": \"user.created\",\n          \"payload\": {\n            \"id\": \"%s\",\n            \"email\": \"%s\"\n          }\n        }\n        \"\"\".formatted(userId, email);\n",[26,865,866,875,879,884,889,894,899,904,908],{"__ignoreMap":24},[29,867,868,871,873],{"class":31,"line":32},[29,869,870],{"class":42},"String body ",[29,872,47],{"class":46},[29,874,193],{"class":50},[29,876,877],{"class":31,"line":39},[29,878,198],{"class":50},[29,880,881],{"class":31,"line":64},[29,882,883],{"class":50},"          \"event\": \"user.created\",\n",[29,885,886],{"class":31,"line":97},[29,887,888],{"class":50},"          \"payload\": {\n",[29,890,891],{"class":31,"line":118},[29,892,893],{"class":50},"            \"id\": \"%s\",\n",[29,895,896],{"class":31,"line":127},[29,897,898],{"class":50},"            \"email\": \"%s\"\n",[29,900,901],{"class":31,"line":134},[29,902,903],{"class":50},"          }\n",[29,905,906],{"class":31,"line":151},[29,907,213],{"class":50},[29,909,910,912,914,916],{"class":31,"line":163},[29,911,218],{"class":50},[29,913,762],{"class":42},[29,915,765],{"class":385},[29,917,918],{"class":42},"(userId, email);\n",[15,920,921,922,924],{},"No ",[26,923,70],{}," escapes around JSON field names and values.",[576,926,928],{"id":927},"html-email-template","HTML email template",[19,930,932],{"className":21,"code":931,"language":23,"meta":24,"style":24},"String html = \"\"\"\n        \u003C!DOCTYPE html>\n        \u003Chtml lang=\"en\">\n        \u003Chead>\u003Ctitle>%s\u003C\u002Ftitle>\u003C\u002Fhead>\n        \u003Cbody>\n          \u003Ch1>Hello, %s!\u003C\u002Fh1>\n          \u003Cp>Your order #%s has been shipped.\u003C\u002Fp>\n        \u003C\u002Fbody>\n        \u003C\u002Fhtml>\n        \"\"\".formatted(subject, firstName, orderId);\n",[26,933,934,943,948,953,958,963,968,973,978,983],{"__ignoreMap":24},[29,935,936,939,941],{"class":31,"line":32},[29,937,938],{"class":42},"String html ",[29,940,47],{"class":46},[29,942,193],{"class":50},[29,944,945],{"class":31,"line":39},[29,946,947],{"class":50},"        \u003C!DOCTYPE html>\n",[29,949,950],{"class":31,"line":64},[29,951,952],{"class":50},"        \u003Chtml lang=\"en\">\n",[29,954,955],{"class":31,"line":97},[29,956,957],{"class":50},"        \u003Chead>\u003Ctitle>%s\u003C\u002Ftitle>\u003C\u002Fhead>\n",[29,959,960],{"class":31,"line":118},[29,961,962],{"class":50},"        \u003Cbody>\n",[29,964,965],{"class":31,"line":127},[29,966,967],{"class":50},"          \u003Ch1>Hello, %s!\u003C\u002Fh1>\n",[29,969,970],{"class":31,"line":134},[29,971,972],{"class":50},"          \u003Cp>Your order #%s has been shipped.\u003C\u002Fp>\n",[29,974,975],{"class":31,"line":151},[29,976,977],{"class":50},"        \u003C\u002Fbody>\n",[29,979,980],{"class":31,"line":163},[29,981,982],{"class":50},"        \u003C\u002Fhtml>\n",[29,984,985,987,989,991],{"class":31,"line":240},[29,986,218],{"class":50},[29,988,762],{"class":42},[29,990,765],{"class":385},[29,992,993],{"class":42},"(subject, firstName, orderId);\n",[576,995,997],{"id":996},"test-fixture-data","Test fixture data",[19,999,1001],{"className":21,"code":1000,"language":23,"meta":24,"style":24},"@Test\nvoid parsesConfig() {\n    String yaml = \"\"\"\n            server:\n              port: 8080\n              host: localhost\n            database:\n              url: jdbc:h2:mem:test\n            \"\"\";\n    Config cfg = Config.parse(yaml);\n    assertThat(cfg.serverPort()).isEqualTo(8080);\n}\n",[26,1002,1003,1011,1022,1031,1036,1041,1046,1051,1056,1062,1078,1103],{"__ignoreMap":24},[29,1004,1005,1008],{"class":31,"line":32},[29,1006,1007],{"class":42},"@",[29,1009,1010],{"class":46},"Test\n",[29,1012,1013,1016,1019],{"class":31,"line":39},[29,1014,1015],{"class":46},"void",[29,1017,1018],{"class":385}," parsesConfig",[29,1020,1021],{"class":42},"() {\n",[29,1023,1024,1027,1029],{"class":31,"line":64},[29,1025,1026],{"class":42},"    String yaml ",[29,1028,47],{"class":46},[29,1030,193],{"class":50},[29,1032,1033],{"class":31,"line":97},[29,1034,1035],{"class":50},"            server:\n",[29,1037,1038],{"class":31,"line":118},[29,1039,1040],{"class":50},"              port: 8080\n",[29,1042,1043],{"class":31,"line":127},[29,1044,1045],{"class":50},"              host: localhost\n",[29,1047,1048],{"class":31,"line":134},[29,1049,1050],{"class":50},"            database:\n",[29,1052,1053],{"class":31,"line":151},[29,1054,1055],{"class":50},"              url: jdbc:h2:mem:test\n",[29,1057,1058,1060],{"class":31,"line":163},[29,1059,424],{"class":50},[29,1061,124],{"class":42},[29,1063,1064,1067,1069,1072,1075],{"class":31,"line":240},[29,1065,1066],{"class":42},"    Config cfg ",[29,1068,47],{"class":46},[29,1070,1071],{"class":42}," Config.",[29,1073,1074],{"class":385},"parse",[29,1076,1077],{"class":42},"(yaml);\n",[29,1079,1080,1083,1086,1089,1092,1095,1098,1101],{"class":31,"line":246},[29,1081,1082],{"class":385},"    assertThat",[29,1084,1085],{"class":42},"(cfg.",[29,1087,1088],{"class":385},"serverPort",[29,1090,1091],{"class":42},"()).",[29,1093,1094],{"class":385},"isEqualTo",[29,1096,1097],{"class":42},"(",[29,1099,1100],{"class":54},"8080",[29,1102,789],{"class":42},[29,1104,1105],{"class":31,"line":252},[29,1106,431],{"class":42},[15,1108,1109],{},"Test fixtures defined as text blocks are readable and maintainable — no escaping, no\nexternal files for simple cases.",[10,1111,1113],{"id":1112},"trailing-whitespace-the-subtle-gotcha","Trailing whitespace — the subtle gotcha",[15,1115,1116,1117,1120],{},"The compiler strips ",[172,1118,1119],{},"trailing whitespace"," from every line automatically. If you write:",[19,1122,1124],{"className":21,"code":1123,"language":23,"meta":24,"style":24},"String s = \"\"\"\n        hello   \n        world   \n        \"\"\";\n",[26,1125,1126,1135,1140,1145],{"__ignoreMap":24},[29,1127,1128,1131,1133],{"class":31,"line":32},[29,1129,1130],{"class":42},"String s ",[29,1132,47],{"class":46},[29,1134,193],{"class":50},[29,1136,1137],{"class":31,"line":39},[29,1138,1139],{"class":50},"        hello   \n",[29,1141,1142],{"class":31,"line":64},[29,1143,1144],{"class":50},"        world   \n",[29,1146,1147,1149],{"class":31,"line":97},[29,1148,218],{"class":50},[29,1150,124],{"class":42},[15,1152,1153,1154,1157,1158,1160],{},"Those trailing spaces after \"hello\" and \"world\" are silently removed. The result is\n",[26,1155,1156],{},"\"hello\\nworld\\n\"",". If you need a trailing space to be preserved, use ",[26,1159,627],{},":",[19,1162,1164],{"className":21,"code":1163,"language":23,"meta":24,"style":24},"String s = \"\"\"\n        hello\\s\n        world\\s\n        \"\"\";\n\u002F\u002F \"hello \\nworld \\n\"\n",[26,1165,1166,1174,1181,1188,1194],{"__ignoreMap":24},[29,1167,1168,1170,1172],{"class":31,"line":32},[29,1169,1130],{"class":42},[29,1171,47],{"class":46},[29,1173,193],{"class":50},[29,1175,1176,1179],{"class":31,"line":39},[29,1177,1178],{"class":50},"        hello",[29,1180,655],{"class":54},[29,1182,1183,1186],{"class":31,"line":64},[29,1184,1185],{"class":50},"        world",[29,1187,655],{"class":54},[29,1189,1190,1192],{"class":31,"line":97},[29,1191,218],{"class":50},[29,1193,124],{"class":42},[29,1195,1196],{"class":31,"line":118},[29,1197,1198],{"class":35},"\u002F\u002F \"hello \\nworld \\n\"\n",[10,1200,1202],{"id":1201},"runtime-string-utilities-added-with-text-blocks","Runtime string utilities added with text blocks",[15,1204,1205,1206,1209],{},"Java 15 also added two ",[26,1207,1208],{},"String"," methods useful for text-block-like processing at runtime:",[15,1211,1212,1217],{},[172,1213,1214],{},[26,1215,1216],{},"String.stripIndent()"," — performs the same incidental whitespace removal as the\ncompiler, useful for strings loaded from files or databases:",[19,1219,1221],{"className":21,"code":1220,"language":23,"meta":24,"style":24},"String fromFile = Files.readString(path);\nString cleaned = fromFile.stripIndent(); \u002F\u002F same as compiler's text-block stripping\n",[26,1222,1223,1239],{"__ignoreMap":24},[29,1224,1225,1228,1230,1233,1236],{"class":31,"line":32},[29,1226,1227],{"class":42},"String fromFile ",[29,1229,47],{"class":46},[29,1231,1232],{"class":42}," Files.",[29,1234,1235],{"class":385},"readString",[29,1237,1238],{"class":42},"(path);\n",[29,1240,1241,1244,1246,1249,1252,1255],{"class":31,"line":39},[29,1242,1243],{"class":42},"String cleaned ",[29,1245,47],{"class":46},[29,1247,1248],{"class":42}," fromFile.",[29,1250,1251],{"class":385},"stripIndent",[29,1253,1254],{"class":42},"(); ",[29,1256,1257],{"class":35},"\u002F\u002F same as compiler's text-block stripping\n",[15,1259,1260,1265,1266,1269],{},[172,1261,1262],{},[26,1263,1264],{},"String.indent(int n)"," — adjusts indentation by ",[26,1267,1268],{},"n"," spaces per line (adds if\npositive, removes if negative):",[19,1271,1273],{"className":21,"code":1272,"language":23,"meta":24,"style":24},"String block = \"line1\\nline2\\n\";\nSystem.out.print(block.indent(4));\n\u002F\u002F     line1\n\u002F\u002F     line2\n",[26,1274,1275,1296,1318,1323],{"__ignoreMap":24},[29,1276,1277,1280,1282,1285,1287,1290,1292,1294],{"class":31,"line":32},[29,1278,1279],{"class":42},"String block ",[29,1281,47],{"class":46},[29,1283,1284],{"class":50}," \"line1",[29,1286,55],{"class":54},[29,1288,1289],{"class":50},"line2",[29,1291,55],{"class":54},[29,1293,58],{"class":50},[29,1295,124],{"class":42},[29,1297,1298,1301,1304,1307,1310,1312,1315],{"class":31,"line":39},[29,1299,1300],{"class":42},"System.out.",[29,1302,1303],{"class":385},"print",[29,1305,1306],{"class":42},"(block.",[29,1308,1309],{"class":385},"indent",[29,1311,1097],{"class":42},[29,1313,1314],{"class":54},"4",[29,1316,1317],{"class":42},"));\n",[29,1319,1320],{"class":31,"line":64},[29,1321,1322],{"class":35},"\u002F\u002F     line1\n",[29,1324,1325],{"class":31,"line":97},[29,1326,1327],{"class":35},"\u002F\u002F     line2\n",[15,1329,1330,1335],{},[172,1331,1332],{},[26,1333,1334],{},"String.translateEscapes()"," — interprets escape sequences in a string at runtime\n(useful when reading escape sequences from config files that should be treated as\nstring content):",[19,1337,1339],{"className":21,"code":1338,"language":23,"meta":24,"style":24},"String raw = \"hello\\\\nworld\"; \u002F\u002F two chars: \\ and n\nString escaped = raw.translateEscapes(); \u002F\u002F \"hello\\nworld\" — actual newline\n",[26,1340,1341,1363],{"__ignoreMap":24},[29,1342,1343,1346,1348,1351,1354,1357,1360],{"class":31,"line":32},[29,1344,1345],{"class":42},"String raw ",[29,1347,47],{"class":46},[29,1349,1350],{"class":50}," \"hello",[29,1352,1353],{"class":54},"\\\\",[29,1355,1356],{"class":50},"nworld\"",[29,1358,1359],{"class":42},"; ",[29,1361,1362],{"class":35},"\u002F\u002F two chars: \\ and n\n",[29,1364,1365,1368,1370,1373,1376,1378],{"class":31,"line":39},[29,1366,1367],{"class":42},"String escaped ",[29,1369,47],{"class":46},[29,1371,1372],{"class":42}," raw.",[29,1374,1375],{"class":385},"translateEscapes",[29,1377,1254],{"class":42},[29,1379,1380],{"class":35},"\u002F\u002F \"hello\\nworld\" — actual newline\n",[10,1382,1384],{"id":1383},"version-history","Version history",[1386,1387,1388,1404],"table",{},[1389,1390,1391],"thead",{},[1392,1393,1394,1398,1401],"tr",{},[1395,1396,1397],"th",{},"Java",[1395,1399,1400],{},"Status",[1395,1402,1403],{},"Details",[1405,1406,1407,1419,1434],"tbody",{},[1392,1408,1409,1413,1416],{},[1410,1411,1412],"td",{},"13",[1410,1414,1415],{},"Preview",[1410,1417,1418],{},"JEP 355 — first preview",[1392,1420,1421,1424,1426],{},[1410,1422,1423],{},"14",[1410,1425,1415],{},[1410,1427,1428,1429,1431,1432],{},"JEP 368 — second preview, added ",[26,1430,582],{}," and ",[26,1433,627],{},[1392,1435,1436,1439,1444],{},[1410,1437,1438],{},"15",[1410,1440,1441],{},[172,1442,1443],{},"Standard",[1410,1445,1446,1447,1450],{},"JEP 378 — finalized, no ",[26,1448,1449],{},"--enable-preview"," needed",[15,1452,1453],{},"Use text blocks freely in any codebase targeting Java 15+.",[10,1455,1457],{"id":1456},"recap","Recap",[15,1459,1460,1462,1463,1465,1466,1469,1470,1472,1473,1475,1476,1478,1479,1481,1482,1485,1486,1489,1490,1493,1494,1497],{},[172,1461,174],{}," are triple-quoted multi-line string literals that produce a regular\n",[26,1464,1208],{}," — same type, same pool, no runtime overhead. The compiler strips ",[172,1467,1468],{},"incidental\nwhitespace"," based on the common leading prefix and the position of the closing ",[26,1471,269],{},",\nand normalises all line endings to ",[26,1474,55],{},". Two new escape sequences cover edge cases: ",[26,1477,582],{},"\nsuppresses a newline (line continuation) and ",[26,1480,627],{}," preserves a trailing space. Use\n",[26,1483,1484],{},".formatted()"," for value substitution. Text blocks are the right choice for any\nmulti-line content — SQL, JSON, HTML, test fixtures — and ",[26,1487,1488],{},"stripIndent()"," \u002F\n",[26,1491,1492],{},"translateEscapes()"," \u002F ",[26,1495,1496],{},"indent()"," extend the same ergonomics to runtime string handling.",[1499,1500,1501],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":24,"searchDepth":39,"depth":39,"links":1503},[1504,1505,1506,1507,1508,1514,1515,1521,1522,1523,1524],{"id":12,"depth":39,"text":13},{"id":262,"depth":39,"text":263},{"id":364,"depth":39,"text":365},{"id":496,"depth":39,"text":497},{"id":570,"depth":39,"text":571,"children":1509},[1510,1512],{"id":578,"depth":64,"text":1511},"Line continuation: \\",{"id":623,"depth":64,"text":1513},"Space anchor: \\s",{"id":683,"depth":39,"text":684},{"id":795,"depth":39,"text":796,"children":1516},[1517,1518,1519,1520],{"id":799,"depth":64,"text":800},{"id":859,"depth":64,"text":860},{"id":927,"depth":64,"text":928},{"id":996,"depth":64,"text":997},{"id":1112,"depth":39,"text":1113},{"id":1201,"depth":39,"text":1202},{"id":1383,"depth":39,"text":1384},{"id":1456,"depth":39,"text":1457},"Everything about Java text blocks — triple-quote syntax, incidental whitespace stripping, line-ending normalisation, new escape sequences, formatted(), trailing-space handling, and practical examples with SQL, JSON, and HTML.","easy","md",{},"\u002Fblog\u002Fjava-text-blocks","\u002Fjava\u002Fmodern-java\u002Ftext-blocks",{"title":5,"description":1525},"blog\u002Fjava-text-blocks","Text Blocks","Modern Java","modern-java","2026-06-20","3HP_xgWOkVvK5OdSforJ59tKn4TKIoSb86znomurax8",1782244091077]