String Search

Su Suriya Ravichandran Updated 14 Nov 2026
6 min read ·Lesson 96 of 133

Searching a string means finding whether some text appears in it, and usually where. Java's String class provides methods for every common case: checking for presence (contains), checking the beginning or end (startsWith, endsWith), finding positions (indexOf, lastIndexOf), and testing patterns (matches). Combined with substring, these methods are the basic tools for parsing log lines, file paths, URLs, and any text with a known structure.

Checking Whether Text Is Present

String line = "2026-11-11 10:05:42 ERROR Payment failed: timeout";

line.contains("ERROR");           // true
line.contains("error");           // false: searches are case-sensitive
line.startsWith("2026-11-11");    // true
line.endsWith("timeout");         // true
line.startsWith("ERROR", 20);     // true: check for a prefix at a given index
MethodReturns true when
contains(s)s appears anywhere
startsWith(prefix)The string begins with prefix
startsWith(prefix, offset)prefix appears starting exactly at offset
endsWith(suffix)The string ends with suffix
isEmpty() / isBlank()Nothing to search

Every string contains the empty string, so s.contains("") is always true. Passing null to these methods throws NullPointerException.

Case-insensitive presence checks

There is no containsIgnoreCase. Convert both sides to the same case first:

boolean found = text.toLowerCase(Locale.ROOT).contains(term.toLowerCase(Locale.ROOT));

Locale.ROOT makes the conversion behave the same on every machine. For prefix or suffix checks, regionMatches(true, ...) compares without creating new strings:

String file = "REPORT.PDF";
boolean isPdf = file.regionMatches(true, file.length() - 4, ".pdf", 0, 4);   // true

Finding Positions: indexOf and lastIndexOf

indexOf returns the index where text first appears, or -1 if it does not appear:

String s = "banana";

s.indexOf("an");          // 1
s.indexOf("an", 2);       // 3: start searching from index 2
s.lastIndexOf("an");      // 3: last occurrence
s.indexOf("x");           // -1: not found
s.indexOf('n');           // 2: a char works too
MethodSearches
indexOf(str) / indexOf(ch)From the start
indexOf(str, fromIndex)From fromIndex forward
lastIndexOf(str)From the end backward
lastIndexOf(str, fromIndex)Backward, starting at fromIndex

Since Java 21, indexOf(str, beginIndex, endIndex) limits the search to a range.

Always check for -1 before using the result. Passing -1 to substring or charAt throws StringIndexOutOfBoundsException:

int at = email.indexOf('@');
if (at == -1) {
    throw new IllegalArgumentException("Not an email: " + email);
}
String domain = email.substring(at + 1);

Finding every occurrence

Call indexOf repeatedly, starting each search just after the previous match:

static int countOccurrences(String text, String word) {
    int count = 0;
    int from = 0;
    while ((from = text.indexOf(word, from)) != -1) {
        count++;
        from += word.length();        // move past this match
    }
    return count;
}

Advancing by word.length() counts non-overlapping matches. Advancing by 1 instead counts overlapping ones: in "aaa", "aa" occurs once without overlap but twice with overlap ("aaa".indexOf("aa", 1) is 1).

Extracting Text Around a Match

Search methods are most useful combined with substring. A common pattern extracts the text between two markers:

static String between(String text, String start, String end) {
    int s = text.indexOf(start);
    if (s == -1) return null;
    s += start.length();                 // skip past the start marker
    int e = text.indexOf(end, s);        // search for the end marker after it
    return e == -1 ? null : text.substring(s, e);
}

between("[user=ada] Login", "[user=", "]");     // "ada"

Splitting a path or file name at its last separator:

String path = "/var/log/app/server.log";
String fileName = path.substring(path.lastIndexOf('/') + 1);        // "server.log"
String dir = path.substring(0, path.lastIndexOf('/'));              // "/var/log/app"

int dot = fileName.lastIndexOf('.');
String ext = dot == -1 ? "" : fileName.substring(dot + 1);          // "log"

lastIndexOf is the right choice for extensions, because names like archive.tar.gz contain more than one dot. (For real file paths, java.nio.file.Path provides getFileName() and getParent().)

Pattern Searches: matches and Regular Expressions

matches(regex) tests whether the entire string matches a regular expression:

"ERROR".matches("ERR.*");                         // true
"600042".matches("\\d{6}");                       // true: exactly six digits
"order 42".matches("\\d+");                       // false: the whole string must match

To find a pattern anywhere in a string, or to extract matched parts, use java.util.regex.Pattern and Matcher:

Pattern p = Pattern.compile("(\\d+)ms");
Matcher m = p.matcher("WARN Slow query 2310ms on /api/orders");
if (m.find()) {
    int millis = Integer.parseInt(m.group(1));    // 2310
}

Regular expressions have their own lesson. For fixed text, prefer contains and indexOf: they are simpler and faster.

Searching Character by Character

For conditions that are about kinds of characters rather than specific text, loop over the characters or use chars():

boolean hasDigit = password.chars().anyMatch(Character::isDigit);
long vowels = word.toLowerCase().chars().filter(c -> "aeiou".indexOf(c) >= 0).count();
int firstSpace = text.indexOf(' ');

Performance Notes

  • contains, indexOf, startsWith, and endsWith scan the string; their cost grows with its length. For a few searches on ordinary text this is negligible.
  • Searching the same large text for many different words is faster with a different structure, such as splitting it into words and putting them in a HashSet.
  • String.matches compiles its regular expression on every call. Inside loops, compile a Pattern once and reuse it.

Practical Example: Scanning Application Logs

import java.util.*;

public class LogScan {
    static int countOccurrences(String text, String word) {
        int count = 0;
        int from = 0;
        while ((from = text.indexOf(word, from)) != -1) {
            count++;
            from += word.length();
        }
        return count;
    }

    static String between(String text, String start, String end) {
        int s = text.indexOf(start);
        if (s == -1) return null;
        s += start.length();
        int e = text.indexOf(end, s);
        return e == -1 ? null : text.substring(s, e);
    }

    public static void main(String[] args) {
        List<String> log = List.of(
            "2026-11-11 10:02:15 INFO  [user=ada] Login success",
            "2026-11-11 10:05:42 ERROR [user=linus] Payment failed: timeout",
            "2026-11-11 10:06:03 WARN  [user=grace] Slow query 2310ms",
            "2026-11-11 10:07:19 ERROR [user=ada] Payment failed: card declined",
            "2026-11-11 10:09:55 INFO  [user=linus] Logout"
        );

        for (String line : log) {
            if (line.contains(" ERROR ")) {
                String user = between(line, "[user=", "]");
                String reason = line.substring(line.lastIndexOf(':') + 1).strip();
                String time = line.substring(11, 19);
                System.out.println(time + " " + user + " -> " + reason);
            }
        }

        String all = String.join("\n", log);
        System.out.println("'Payment' appears " + countOccurrences(all, "Payment") + " times");
        System.out.println("Lines for ada: " + log.stream().filter(l -> l.contains("[user=ada]")).count());
        System.out.println("Starts with today: " + log.stream().allMatch(l -> l.startsWith("2026-11-11")));
    }
}

Output:

10:05:42 linus -> timeout
10:07:19 ada -> card declined
'Payment' appears 2 times
Lines for ada: 2
Starts with today: true

Each search method extracts one piece of information:

  • contains(" ERROR ") selects error lines. The surrounding spaces stop it from matching a word like ERRORS.
  • between uses two indexOf calls to pull the user name out of the [user=...] tag; the second search starts after the first marker.
  • lastIndexOf(':') finds the colon before the failure reason. It must be the last colon, because the timestamp also contains colons.
  • substring(11, 19) extracts the time from its fixed position in the line format.
  • countOccurrences loops with indexOf(word, from).
  • startsWith verifies that every line is from the expected date.

This combination of searching for markers and cutting with substring is enough for many lightweight parsing tasks. For more complex or variable formats, regular expressions are the next step.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about String Search and get answers drawn from it.

Signed-in readers only.