JAVA / STRINGS AND TEXT HANDLING
Comparing, searching and checking string content
Pick the right String test for each question: equals for identity, compareTo for ordering, indexOf and friends for locating text inside a string.
What you will learn
- Use equals or equalsIgnoreCase for yes/no identity, compareTo only for ordering
- Judge compareTo by its sign against 0, never by the exact number it returns
- Find every occurrence by looping indexOf(needle, from) and testing for -1
- Separate isEmpty (length 0) from isBlank (whitespace only, Java 11+)
Understanding Comparing, searching and checking string content
equals asks a single question: do these two strings hold the same characters in the same order? It rules out different lengths first, then compares code unit by code unit, so it never cares how either string was built. equalsIgnoreCase does the same walk but folds each character to upper case and then lower case before comparing, which makes it per-character and locale-independent, unlike toLowerCase() which follows the default locale. One consequence of equals being an instance method: name.equals("root") throws NullPointerException when name is null, so write "root".equals(name) or Objects.equals(name, "root") when null is possible.
compareTo answers a different question, which string comes first, and hands back an int instead of a boolean. It walks both strings until it hits a mismatching character and returns that character's numeric difference; if one string runs out first, it returns the difference in lengths instead. Because the comparison is on raw UTF-16 code unit values, every uppercase ASCII letter (65 to 90) sorts ahead of every lowercase one (97 to 122), so "Zebra" comes before "apple" in natural order. Only the sign is part of the contract, which is why you test compareTo(...) < 0 and never == -1.
The search methods all reduce to one primitive: scan for a match and report where it starts. indexOf returns the first index, lastIndexOf the highest, and both return -1 for no match, which is exactly why contains(x) is indexOf(x) >= 0 and why startsWith and endsWith are the same scan pinned to a fixed position. Giving indexOf a second argument turns it into a resumable search, and that is how you walk every occurrence in a loop. Emptiness checks sit nearby but ask something else: isEmpty() means length() == 0, while isBlank() is also true for a string of spaces or tabs, which is what you usually want for form input.
public class StringChecks {
public static void main(String[] args) {
String a = "Ferrous";
String b = "ferrous";
System.out.println(a.equals(b));
System.out.println(a.equalsIgnoreCase(b));
System.out.println(a.compareTo(b));
System.out.println(a.compareToIgnoreCase(b));
String log = "GET /index.html 404 not found";
System.out.println(log.indexOf("404"));
System.out.println(log.indexOf("500"));
System.out.println(log.contains("not found"));
System.out.println(log.startsWith("GET"));
System.out.println(log.endsWith(".html"));
String user = " ";
System.out.println(user.isEmpty() + " " + user.isBlank());
}
}Every string check is one of three questions, same characters (equals), which comes first (compareTo), or where does it occur (indexOf), and the rest of the API is convenience over those three.
Worked examples
Why uppercase sorts first
Shows that natural String ordering is code unit ordering, and how a comparator changes it.
import java.util.Arrays;
public class OrderingDemo {
public static void main(String[] args) {
String[] names = {"apple", "Banana", "Apple", "banana"};
Arrays.sort(names);
System.out.println(Arrays.toString(names));
Arrays.sort(names, String::compareToIgnoreCase);
System.out.println(Arrays.toString(names));
System.out.println("Zebra".compareTo("apple"));
}
}Example explained
Line 1Arrays.sort with no comparator uses compareTo, so 'A' (65) and 'B' (66) both land ahead of 'a' (97).
Line 2String::compareToIgnoreCase is read as (x, y) -> x.compareToIgnoreCase(y), so the second sort groups the case variants together.
Line 3That sort is stable, so Apple stays ahead of apple even though the comparator calls them equal.
Line 4"Zebra".compareTo("apple") is 90 - 97 = -7: the sign says Zebra comes first, the 7 itself carries no meaning.
Finding every occurrence
Demonstrates the resumable form of indexOf and how the step size decides whether overlaps count.
public class FindAll {
public static void main(String[] args) {
String seq = "ATATATAT";
String pattern = "ATAT";
int from = 0;
int hit;
while ((hit = seq.indexOf(pattern, from)) != -1) {
System.out.println("overlapping hit at " + hit);
from = hit + 1;
}
from = 0;
while ((hit = seq.indexOf(pattern, from)) != -1) {
System.out.println("stepped hit at " + hit);
from = hit + pattern.length();
}
System.out.println("lastIndexOf: " + seq.lastIndexOf(pattern));
}
}Example explained
Line 1indexOf(pattern, from) begins the scan at from, which is what makes repeated searching possible without cutting the string up.
Line 2from = hit + 1 re-examines the characters inside the last match, so overlapping hits at 0, 2 and 4 are all reported.
Line 3from = hit + pattern.length() jumps past the match, so the overlap at 2 is skipped and only 0 and 4 appear.
Line 4lastIndexOf searches from the end and returns 4, the highest starting index where the pattern still fits.
Case-insensitive prefix without copying
Compares three ways to check a prefix while ignoring case, and why one of them is locale-sensitive.
public class PrefixCheck {
public static void main(String[] args) {
String header = "Content-Type: text/html";
String want = "content-type:";
System.out.println("startsWith: " + header.startsWith(want));
System.out.println("regionMatches: " + header.regionMatches(true, 0, want, 0, want.length()));
System.out.println("lowercased: " + header.toLowerCase().startsWith(want));
System.out.println("ignoreCase eq: " + "TITLE".equalsIgnoreCase("title"));
}
}Example explained
Line 1startsWith has no ignore-case overload, so the capitalised header fails against a lowercase prefix.
Line 2regionMatches(true, 0, want, 0, len) compares a fixed window of both strings with case folding and creates no new string.
Line 3toLowerCase().startsWith(...) works but allocates a copy and uses the default locale, where Turkish turns I into a dotless i.
Line 4equalsIgnoreCase folds one character at a time, so its answer does not depend on the locale at all.
Important notes
isBlank() and strip() need Java 11; the older idiom is s.trim().isEmpty(), and trim only removes characters up to U+0020, not every Unicode space.
compareTo orders by code unit rather than by alphabet, so accented and non-Latin text sorts strangely for readers; use java.text.Collator for user-facing sorting.
Common mistakes
Writing if (line.indexOf("@") > 0) instead of != -1: a match at the very start returns 0, so "@admin" is reported as containing no @ at all.
Testing compareTo(...) == -1: "Apple".compareTo("apple") is -32, so the branch never runs and the ordering logic falls into the wrong case.
Passing a regex to contains: "abc123".contains("[0-9]") is false because contains looks for those five literal characters; a digit test needs matches(".*[0-9].*") or a Pattern.
Try it yourself
Change, predict, then run
Take String s = "this is his list" and print every index where "is" occurs by looping indexOf with a from argument. Then print whether s starts with "THIS" using regionMatches with case folding turned on.
Open the Java workspaceCheck your understanding
Sorting {"banana", "Cherry", "apple"} with Arrays.sort produces [Cherry, apple, banana]. What explains that order?
- Arrays.sort compares case-insensitively first and only looks at case to break ties.
- compareTo orders by length before content, so the six-letter words end up around the five-letter one.
- compareTo returns the difference of the first mismatching UTF-16 code unit, and 'C' is 67 while 'a' is 97 and 'b' is 98.
- Uppercase literals are interned in a separate table that gets scanned before lowercase ones.
Show answer
Natural ordering is compareTo, which stops at the first differing character and returns the difference of the code units, so any uppercase ASCII letter beats any lowercase one and Cherry lands first. Option 0 is tempting because compareToIgnoreCase exists, but case folding only happens when you pass that comparator explicitly; the no-argument sort never applies it.