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.

FunctionDescription
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.