substring()

Copies part of a String between a start index (inclusive) and an end index (exclusive) into a new String.

Signature
public String substring(int beginIndex, int endIndex)
  • Added in Java Java 1.0

Parameters

ParameterTypeDescription
$beginIndexintIndex of the first char to copy (inclusive).
$endIndexoptionalint= length()Index just after the last char to copy (exclusive). The one-argument overload substring(int beginIndex) uses length().

Return value

StringA new String with the chars from beginIndex up to, but not including, endIndex. Returns this String when the range covers all of it.

Errors

IndexOutOfBoundsException (thrown as StringIndexOutOfBoundsException) if beginIndex is negative, endIndex is greater than length(), or beginIndex is greater than endIndex.

substring(begin, end) returns a new String with the chars from begin up to but not including end. The one-argument form substring(begin) runs to the end. The length of the result is always end - begin, which is why the end is exclusive: s.substring(0, 3) gives the first three chars.

"/api/v2/orders".substring(5)      // "v2/orders"
"/api/v2/orders".substring(5, 7)   // "v2"

The range rules

Both indexes must satisfy 0 <= begin <= end <= length(), otherwise you get StringIndexOutOfBoundsException. Equal indexes are allowed and give an empty String, so s.substring(s.length()) is "", not an error. The usual crash is truncating a short value: s.substring(0, 100) throws when s has fewer than 100 chars. Clamp with Math.min(s.length(), 100).

Pair it with indexOf

Most real uses find a position first and cut around it: the part of a header after "Bearer ", the key and value either side of the first =, the id after the last /. Always handle indexOf returning -1, because substring(-1 + 1) silently returns the whole String and substring(0, -1) throws.

Memory

Since Java 7 update 6, substring copies the chars it needs. Older versions shared the parent's array, so keeping a small substring of a huge document kept the whole document in memory. That leak is gone, but each call now allocates, which is worth knowing in a parser that slices millions of tokens.

Code points

Indexes are char indexes. Cutting between the two halves of a surrogate pair produces a broken character. When truncating user text for display, find the end with offsetByCodePoints, as shown in the length() lesson.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Extracting the resource id after the last slash of a request path.
  • Truncating an error message to the size of a database column before saving it.
  • Showing only the last four digits of a card or token in logs and receipts.

Frequently asked questions

Is the end index of substring inclusive or exclusive?

Exclusive. substring(2, 5) returns the chars at indexes 2, 3 and 4, and its length is end - begin, which is 3.

How do I get the last n characters of a String?

Use s.substring(s.length() - n) after checking that s.length() >= n; otherwise the begin index is negative and the call throws StringIndexOutOfBoundsException.

Does substring cause memory leaks in Java?

Not since Java 7 update 6. substring now copies the chars it returns, so a small substring no longer keeps a large parent String alive.

More examples

Splitting key=value at the first equals sign

Cutting at the first '=' keeps any later '=' inside the value, which a naive split("=") would break apart. The -1 check matters because substring(0, -1) throws.

Example · java
void main() {
    for (String line : List.of("DB_URL=jdbc:postgresql://db/app?ssl=true", "DEBUG=", "BROKEN")) {
        int eq = line.indexOf('=');
        if (eq < 0) {
            IO.println("skip: " + line);
            continue;
        }
        String key = line.substring(0, eq);
        String value = line.substring(eq + 1);
        IO.println(key + " -> [" + value + "]");
    }
}
// Output:
// DB_URL -> [jdbc:postgresql://db/app?ssl=true]
// DEBUG -> []
// skip: BROKEN

Masking a secret for logs

substring(length() - 4) takes the last four chars. Short values are fully masked, because showing the last four of a four-char secret would show all of it.

Example · java
String mask(String secret) {
    if (secret.length() <= 4) return "****";
    return "*".repeat(secret.length() - 4) + secret.substring(secret.length() - 4);
}

void main() {
    IO.println(mask("4111111111111111"));
    IO.println(mask("sk_test_abc123"));
    IO.println(mask("abc"));
}
// Output:
// ************1111
// **********c123
// ****

Truncating without an exception

Both forms guard the end index against the real length. That is the fix for the common crash when an error message or user field is shorter than the column limit you truncate to.

Example · java
String truncate(String s, int max) {
    return s.length() <= max ? s : s.substring(0, max);
}

void main() {
    String error = "Order failed: payment provider timeout";
    IO.println(truncate(error, 13));
    IO.println(truncate("ok", 13));
    IO.println(error.substring(0, Math.min(error.length(), 100)));
}
// Output:
// Order failed:
// ok
// Order failed: payment provider timeout

Discussion

  • Be the first to comment on this lesson.