substring()
Copies part of a String between a start index (inclusive) and an end index (exclusive) into a new String.
public String substring(int beginIndex, int endIndex)- Added in Java Java 1.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$beginIndex | int | Index of the first char to copy (inclusive). |
$endIndexoptional | int= 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
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.
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: BROKENMasking 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.
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.
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