Text
Strings in J2 are called text: immutable, UTF-8, with a small literal syntax, a formatting builtin, and a module of operations for everything else.
Literals
Text is written in double quotes. Exactly five escapes exist: \\,
\", \n, \t, and \r. Any other
backslash sequence is a syntax error, so a typo cannot silently become a literal
backslash.
path = "C:\\temp"
line = "one\ttwo\n"
quote = "she said \"yes\""
print(quote)
Triple-quoted text spans multiple lines verbatim, escapes included; what you see is exactly the string you get:
usage = """usage: report FILE
-v verbose
-q quiet"""
print(usage)
There is no interpolation inside literals; building text from values is
fmt's job.
Formatting with fmt
fmt(template, args...) replaces each {} in the template
with the next argument, rendered the way print would render it. The
placeholder count must match the argument count exactly, or fmt raises
ValueError; mismatches are bugs, and J2 treats them as such.
name = "Ada"
print(fmt("Hello, {}. You have {} messages.", name, 3))
print(fmt("{}% of {} is {}", 25, 80, 80 * 25 / 100))
Basic operations
The everyday operations are global builtins:
s = " Hello, World "
print(len(s)) # 16
print(trim(s)) # Hello, World
print(upper("j2")) # J2
print(lower("LOUD")) # loud
print("j" + "2") # j2, concatenation
print(contains("hello", "ell")) # true
split and join convert between text and sequences. Splitting
on empty text yields the individual characters:
parts = split("a,b,c", ",")
print(parts) # [a,b,c]
print(join(parts, " + ")) # a + b + c
print(split("abc", "")) # [a,b,c]
Indexing and slicing
Indexing text yields one-character text values, zero based; out-of-range or negative
indexes raise IndexError. slice(s, i, j) takes characters from
i up to but not including j. To walk characters in a loop, go
through text.chars, which returns a seq.
word = "grapefruit"
print(word[0]) # g
print(slice(word, 0, 5)) # grape
print(reverse("abc")) # cba
vowels := 0
for ch in text.chars(word) {
if contains("aeiou", ch) { vowels += 1 }
}
print(vowels) # 4
Comparison and conversion
Text compares lexicographically with the ordinary operators, which is what
sort uses on a sequence of text. num parses text to a number
and raises ConversionError when it cannot; str goes the other
way.
print("apple" < "banana") # true
print(sort(["fig", "apple"])) # [apple,fig]
print(num("42") + num("3.5")) # 45.5
print(str(3.5) + " units") # 3.5 units
The text module
Richer operations live in the text module, available everywhere without
imports. The name is deliberate: str is the conversion builtin, and
text is the module.
| Function | Description |
|---|---|
text.find(s, sub) | index of the first occurrence, or null |
text.rfind(s, sub) | index of the last occurrence, or null |
text.starts_with(s, p) text.ends_with(s, p) | prefix and suffix tests |
text.replace(s, find, with) | replace every occurrence |
text.replace_n(s, find, with, n) | replace at most n occurrences |
text.pad_left(s, n) text.pad_right(s, n) | pad with spaces to width n |
text.repeat(s, n) | s repeated n times |
text.chars(s) | seq of one-character texts |
text.bytes(s) text.from_bytes(b) | UTF-8 bytes as a seq of integers, and back |
text.parse_int(s) text.parse_float(s) | strict single-type parsing |
text.is_empty(s) text.is_alpha(s) text.is_digit(s) text.is_alnum(s) | content tests |
s = "report_2026_final.j2"
print(text.starts_with(s, "report")) # true
print(text.replace(s, "_", "-")) # report-2026-final.j2
print(text.pad_left("42", 6)) # four spaces then 42
print(text.find(s, "2026")) # 7
For pattern matching beyond substrings, the regex module provides
match, find, find_all, replace,
split, and groups; see the
Standard Library.