Skip to content
Go forJava Developers

Strings & Formatting

Strings are immutable UTF-8 bytes, not UTF-16 chars. Indexing, runes, building strings efficiently, fmt verbs and strconv.

Section 04 of 315 min read

On this page
  1. Length, indexing and runes
  2. Everyday operations: the strings package
  3. Building strings
  4. Strings and byte slices
  5. Formatting with fmt
  6. Parsing and converting: strconv

A Go string is an immutable sequence of bytes, conventionally holding UTF-8 text. A Java String is a sequence of UTF-16 chars. That one difference explains every string surprise a Java developer hits in Go: len counts bytes, indexing returns a byte, and ranging over a string yields Unicode code points.

Length, indexing and runes

Java — UTF-16 code units
String s = "héllo";

s.length();                  // 5 (UTF-16 units)
s.charAt(1);                 // 'é'
s.codePointCount(0, s.length()); // 5

for (char c : s.toCharArray()) {
    // breaks on emoji: surrogate pairs
}

s.equals("héllo");  // true; == compares references
Go — UTF-8 bytes and runes
s := "héllo"

len(s)                     // 6: é is two bytes
s[1]                       // 195: a byte, not a char
utf8.RuneCountInString(s)  // 5 code points

for i, r := range "hé!" {
    // i = byte offset, r = rune (int32)
    fmt.Println(i, string(r)) // 0 h, 1 é, 3 !
}

s == "héllo" // true: == compares contents

Everyday operations: the strings package

JavaGo
s.contains(x) / s.startsWith(p)strings.Contains(s, x) / strings.HasPrefix(s, p)
s.split(",") (regex!)strings.Split(s, ","): a literal separator, no regex
s.split("\\s+")strings.Fields(s): split on runs of whitespace
s.strip() / s.trim()strings.TrimSpace(s)
s.equalsIgnoreCase(t)strings.EqualFold(s, t)
s.indexOf("=") + two substring callsbefore, after, ok := strings.Cut(s, "=")
String.join(",", list)strings.Join(list, ",")
s.replace(a, b)strings.ReplaceAll(s, a, b)
s.lines()strings.Lines(s): an iterator (Go 1.24)
"ab".repeat(3)strings.Repeat("ab", 3)

Building strings

Java — StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 3; i++) {
    sb.append(i).append(',');
}
String csv = sb.toString(); // "0,1,2,"
Go — strings.Builder
var b strings.Builder // zero value is ready to use
for i := range 3 {
    fmt.Fprintf(&b, "%d,", i) // Builder is an io.Writer
}
csv := b.String() // "0,1,2,"

// Or collect and join:
parts := []string{"a", "b", "c"}
joined := strings.Join(parts, ",")

Strings and byte slices

converting between string and []byte copies the data
b := []byte("hi") // copy: strings are immutable, []byte is not
b[0] = 'H'
s := string(b)     // copy again: "Hi"

// Raw string literals: backticks, no escapes, may span lines.
// The Go equivalent of a Java text block (""" ... """).
query := `
    SELECT id, email
    FROM users
    WHERE email LIKE '%@example.com'`

Formatting with fmt

fmt.Sprintf is String.format with better verbs for inspecting values. %v prints any value in a default format, which is what you reach for while debugging.

the verbs you will use every day
u := User{ID: 7, Name: "Ada"}

fmt.Printf("%v\n", u)   // {7 Ada}                         default format
fmt.Printf("%+v\n", u)  // {ID:7 Name:Ada}                 with field names
fmt.Printf("%#v\n", u)  // main.User{ID:7, Name:"Ada"}     Go syntax
fmt.Printf("%T\n", u)   // main.User                       the type

fmt.Printf("%q\n", "tab\there") // "tab\there"  quoted and escaped
fmt.Printf("%8.2f|\n", 3.14159) // "    3.14|"  width 8, 2 decimals
fmt.Printf("%-6s|\n", "ab")      // "ab    |"   left-aligned
fmt.Printf("%x %08b\n", 255, 5)  // "ff 00000101"

err := fmt.Errorf("load user %d: %w", u.ID, cause) // %w wraps an error

Parsing and converting: strconv

Java — Integer.parseInt and friends
int n = Integer.parseInt("42");
// throws NumberFormatException on bad input

long big = Long.parseLong("9000000000");
double d = Double.parseDouble("2.5");
String s = String.valueOf(99);
boolean ok = Boolean.parseBoolean("true");
Go — strconv
n, err := strconv.Atoi("42x")
// n = 0, err = strconv.Atoi: parsing "42x": invalid syntax

big, err := strconv.ParseInt("9000000000", 10, 64)
d, err := strconv.ParseFloat("2.5", 64)
s := strconv.Itoa(99)                // "99"
ok, err := strconv.ParseBool("true") // "1", "t", "TRUE"… also work
↑ ↓ to navigateEnter to openEsc to close