Back to Blog
Java

Java String Contains: Checking Substrings Safely

java string contains: Learn how to use Java String.contains to check for substrings, handle null and empty arguments, and choose between contains, indexOf, and matches.

String.containssubringindexOfcase-insensitiveJava
Illustration of a magnifying glass over a a Java string highlighting a substring match, representing the contains method.

The java string contains check is one of the most common substring operations in Java. The String.contains(CharSequence) method returns true when the sequence appears in the string,, and false otherwise. It is a literal, case-sensitive search; it does not interpret regular expressions.

The contains Method and Its Contract

contains is defined on String and accepts any CharSequence, including String, StringBuilder, and StringBuffer. The method returns a boolean, so it is often used directly in if statements.

String message = "The quick brown fox"; if (message.contains("quick")) { System.out.println("Found"); }

n The search is for the exact sequence of characters. There is no pattern matching, no wildcard support, and no locale-aware comparison. The standard JDK implementation delegates to indexOf, so the behavior is consistent with a plain substring search.

Because contains returns a boolean, it is not useful when you need the position of the match. For that, use indexOf, which returns the starting index or -1.

Case Sensitivity and Locale Behavior

contains is case-sensitive. "Hello".contains("hello") is false. There is no built-in containsIgnoreCase method, so you must normalize the strings yourself.

The simplest approach is to convert both sides to the same case:

String text = "Hello World"; boolean found = text.toLowerCase().contains("hello".toLowerCase());

This works, but toLowerCase() without a locale can produce unexpected results in Turkish and other locales where I and i have special mappings. For consistent behavior, specify a locale:

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

Alternatively, use regionMatches with the ignoreCase flag to avoid creating new strings. This is covered later.

Null and Empty Arguments

contains throws NullPointerException if the argument is null. The method has no overload that accepts a nullable value, so you must check the argument before calling it.

String text = "abc"; if (text.contains(null)) { // throws NullPointerException }

An empty string is always contained in any string. "anything".contains("") returns true. This follows the same rule as indexOf(""), which returns 0. If your business logic requires that an empty search term be treated as invalid, you need an explicit check:

boolean isValidSearch = term != null && !term.isEmpty() && text.contains(term);

The null check matters because calling contains on a null string also throws:

String text = null; text.contains("a"); // NullPointerException

contains vs indexOf vs matches

The three methods serve different purposes. contains is the clearest when you only need a boolean. indexOf gives you the position and also allows you to search from a specific offset. matches checks whether the entire string matches a regular expression.

MethodReturnsSearch typeUse case
containsbooleanLiteral substringSimple presence check
matchesbooleanFull regex matchPattern validation on the whole string

contains is implemented in terms of indexOf, so for a single presence check there is no meaningful performance difference. matches is much heavier because it compiles and executes a regular expression. Do not use matches just to check for a literal substring.

Performance and Runtime Cost

The standard contains implementation performs a linear scan of the string. The cost is proportional to the length of the text, and the worst case depends on the underlying indexOf algorithm. For typical inputs, this is fast enough.

The main performance concern is repeated calls. If you are checking many different substrings against the same long text, consider whether a single scan with indexOf or a different data structure would be more efficient. For example, if you need to know whether any of several substrings appear, a loop over contains is straightforward but does multiple passes. In that case, a Set of expected tokens or a single regex with alternation may reduce the number of scans.

Another subtle cost is that contains accepts CharSequence. If you pass a StringBuilder, the implementation may need to convert it to a String via toString(). This allocation is usually negligible, but in in a tight loop it can add garbage. If you are repeatedly checking a StringBuilder, convert it to a String once and reuse that reference.

Case-Insensitive Search Without Regex

For a case-insensitive check, regionMatches is often a better choice than converting both strings to lowercase. It avoids creating new strings and lets you specify the region directly.

String text = "Hello World"; String search = "hello"; boolean found = text.regionMatches(true,0, search, 0, search.length());

The first argument enables case-insensitive comparison. The second is the starting offset in text, the third is the search term, the fourth is the offset in the search term, and the last is the number of characters to compare. This method is locale-independent and does not allocate.

If you prefer a more readable expression,, you can use toLowerCase(Locale.ROOT) on both sides. The tradeoff is that it creates two new strings. For one-off checks that is fine; in a loop, regionMatches is more efficient.

Common Edge Cases and Pitfalls

One common mistake is to use contains with a regular expression. contains does not interpret patterns, so "price: 12".contains("\\d+") will not match. Use matches or a Pattern if you need pattern matching.

Another edge case is the empty string. As mentioned, contains("") always returns true. This can cause subtle bugs in validation code where an empty search field is treated as a match.

Also remember that contains is not null-safe. If the string you are searching can be null, guard it first:\n

if (text != null && text.contains("needle")) { // ... }

Finally, be careful with StringBuilder and other CharSequence implementations. contains will call toString() on the argument if it is not already a String. That conversion may have side effects or cost, depending on the implementation. For most use cases, passing a String is the safest and clearest choice.

java string contains: Practical Usage and Code Examples | RYUSLOG DEV