startsWith() and endsWith()

Test whether a String begins or ends with given text, the cheap and readable way to check prefixes, suffixes and file extensions.

Signature
public boolean startsWith(String prefix, int toffset)
  • Added in Java Java 1.0

Parameters

ParameterTypeDescription
$prefixStringThe text that must appear at the start (or at toffset).
$toffsetoptionalint= 0Where to look for the prefix. Negative or too-large values return false.

Return value

booleantrue if the String has prefix at position toffset (0 by default). Always true for an empty prefix within range.

Errors

NullPointerException if prefix is null. endsWith(String suffix) behaves the same way for the end of the String.

startsWith(prefix) and endsWith(suffix) return true when the String begins or ends with that exact text. A second form, startsWith(prefix, offset), checks for the prefix at a given index. They are cheap, they never throw for short input, and they say what you mean, so prefer them over indexOf(x) == 0 or slicing with substring.

Prefix is not path segment

The classic mistake in a router or security filter is path.startsWith("/admin"). It matches /admin/users, and it also matches /administrator and /admin-old. When the prefix is a path, test for an exact match or the prefix followed by a slash. And normalise the path first, because /public/../admin starts with /public.

Case

Both methods are case-sensitive. File extensions and many protocol tokens are not: report.PDF does not end with .pdf. Lowercase with Locale.ROOT first, or use regionMatches(true, ...), which compares a region while ignoring case without creating a copy. HTTP authentication scheme names such as Bearer are case-insensitive, so a strict startsWith("Bearer ") rejects valid clients that send bearer.

Edge cases

  • An empty prefix or suffix always matches.
  • A null argument throws NullPointerException.
  • An offset that is negative or beyond the end returns false rather than throwing.

Checking an extension decides how you display or route a file, but it does not prove what the file contains. Never trust an uploaded file's name for security decisions; check its content and store it under a name you generate.

Both methods also exist on Path, where they compare whole name elements instead of characters: Path.of("/admin/users").startsWith("/admin") is true, but Path.of("/administrator").startsWith("/admin") is false. For file system paths that is usually the comparison you want.

Example

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

When to use it

  • Sending every request under /api/ to the JSON handlers and everything else to static files.
  • Accepting an Authorization header only when it uses the Bearer scheme.
  • Picking a Content-Type header from the extension of a file being served.

Frequently asked questions

How do I use startsWith ignoring case in Java?

Use s.regionMatches(true, 0, prefix, 0, prefix.length()), which compares the start of s with prefix while ignoring case, or lowercase both with Locale.ROOT before calling startsWith.

How do I check a file extension in Java?

name.toLowerCase(Locale.ROOT).endsWith(".pdf"). Include the dot so that a name such as "mypdf" does not match, and remember the extension says nothing reliable about the content.

More examples

Matching a path prefix on a segment boundary

Adding the slash turns a text prefix into a path prefix, so /administrator is no longer treated as part of /admin. Use this shape in hand-written routers and access filters.

Example · java
boolean under(String path, String prefix) {
    return path.equals(prefix) || path.startsWith(prefix + "/");
}

void main() {
    for (String path : List.of("/admin", "/admin/users", "/administrator", "/api/admin")) {
        IO.println(path + " " + under(path, "/admin"));
    }
}
// Output:
// /admin true
// /admin/users true
// /administrator false
// /api/admin false

Reading a Bearer token from an Authorization header

regionMatches(true, ...) is a case-insensitive startsWith, which suits HTTP because scheme names ignore case. A missing header, another scheme or an empty token all give an empty Optional, so the caller can answer 401 in one place.

Example · java
Optional<String> bearer(String header) {
    if (header == null) return Optional.empty();
    String prefix = "Bearer ";
    if (!header.regionMatches(true, 0, prefix, 0, prefix.length())) return Optional.empty();
    String token = header.substring(prefix.length()).strip();
    return token.isEmpty() ? Optional.empty() : Optional.of(token);
}

void main() {
    for (String h : Arrays.asList("Bearer abc.def.ghi", "bearer xyz", "Basic dXNlcjpwYXNz", "Bearer   ", null)) {
        IO.println("[" + h + "] -> " + bearer(h));
    }
}
// Output:
// [Bearer abc.def.ghi] -> Optional[abc.def.ghi]
// [bearer xyz] -> Optional[xyz]
// [Basic dXNlcjpwYXNz] -> Optional.empty
// [Bearer   ] -> Optional.empty
// [null] -> Optional.empty

Choosing a content type by extension

Lowercasing once makes every check case-insensitive. The fallback is a safe binary type, which is what a static file handler should send when it does not recognise the extension.

Example · java
String contentType(String name) {
    String n = name.toLowerCase(Locale.ROOT);
    if (n.endsWith(".json")) return "application/json";
    if (n.endsWith(".html") || n.endsWith(".htm")) return "text/html";
    if (n.endsWith(".png")) return "image/png";
    return "application/octet-stream";
}

void main() {
    for (String name : List.of("data.JSON", "index.htm", "logo.png", "archive.tar.gz")) {
        IO.println(name + " " + contentType(name));
    }
}
// Output:
// data.JSON application/json
// index.htm text/html
// logo.png image/png
// archive.tar.gz application/octet-stream

Discussion

  • Be the first to comment on this lesson.