A lecture for adult learners — this is the reading to work through before
java-strings-exercises.md. Its goal isn't to make you a Java expert; it's to
give you enough working knowledge of the most common String methods that
you can read a chunk of Java code someone (or some AI agent) wrote and
judge, with confidence, whether it does what it claims to do.
We won't cover every method Java has for Strings — just the ones you'll actually run into in real code (and in the exercises that follow this lecture).
A String in Java is a sequence of characters — "hello", "42", "" (an
empty string), all Strings. You've probably already used them without
thinking too hard about it.
The one idea you need to internalize before anything else: Java Strings never change once created. Every method that looks like it's "modifying" a string — making it uppercase, trimming whitespace, replacing characters — is lying to you a little. What it's actually doing is building a brand-new string with the result, and handing that new string back to you. The original string is untouched, forever.
This means code like this does nothing useful:
String name = " ana ";
name.trim(); // creates a new trimmed string... and throws it away
System.out.println(name); // still prints " ana "The new string has to be captured — usually by reassigning the variable:
String name = " ana ";
name = name.trim(); // capture the result
System.out.println(name); // prints "ana"Every method below follows this rule: it returns a new value; it never changes the string you called it on. Keep that in mind and half of Java's String "gotchas" stop being surprising.
Checks whether two strings hold the same characters. This is almost always what you want when asking "are these two strings the same?"
When to use it: any time you're checking whether string content matches — usernames, commands, filenames, anything a human typed or a file produced.
String a = "hello";
String b = "hel" + "lo"; // built at runtime
boolean same = a.equals(b); // true — same charactersif (command.equals("quit")) {
System.out.println("Goodbye!");
}Why not
==?==checks whether two variables point to the exact same object in memory, not whether they hold the same text. It's a common mistake to reach for==here — always use.equals()for content comparison.
Same idea as .equals(), but treats uppercase and lowercase letters as
equivalent.
When to use it: matching user input where case shouldn't matter — e.g. a yes/no prompt, a search term, a case-insensitive command.
String answer = "YES";
boolean confirmed = answer.equalsIgnoreCase("yes"); // trueString fileExt = "TXT";
if (fileExt.equalsIgnoreCase("txt")) {
System.out.println("It's a text file.");
}Returns a negative number if the calling string comes before other
alphabetically, 0 if they're equal, and a positive number if it comes
after. It's the method sorting relies on.
When to use it: sorting strings, or checking alphabetical order.
"apple".compareTo("banana"); // negative — "apple" comes firstpublic static boolean comesBefore(String a, String b) {
return a.compareTo(b) < 0;
}A null-safe version of .equals(). Regular .equals() throws a
NullPointerException if you call it on a null reference. Objects.equals
sidesteps that by checking for null first.
When to use it: whenever either value being compared might legitimately
be null — e.g. an optional field that was never set.
import java.util.Objects;
Objects.equals("cat", "cat"); // true
Objects.equals(null, null); // true — both null counts as equal
Objects.equals(null, "cat"); // false — no exception thrownpublic static boolean sameOrBothNull(String a, String b) {
return Objects.equals(a, b);
}Returns the number of characters in the string.
When to use it: anywhere you need to know "how long is this," including
as the boundary for a loop or a substring() call.
"hello".length(); // 5public static boolean isLongEnough(String password) {
return password.length() >= 8;
}Returns the single character at a given position. Indexing starts at 0, and
the last valid index is length() - 1.
When to use it: inspecting or processing one character at a time — e.g. checking the first letter, or looping character-by-character.
"hello".charAt(0); // 'h'public static void printChars(String s) {
for (int i = 0; i < s.length(); i++) {
System.out.println(s.charAt(i));
}
}Returns a piece of the string. The one-argument form returns everything from
begin to the end. The two-argument form returns everything from begin up
to — but not including — end.
When to use it: pulling out a portion of text you already know the position of — a file extension, the first few characters, everything after a prefix.
"hello world".substring(6); // "world" (from index 6 to the end)
"hello world".substring(0, 5); // "hello" (index 0 up to, not including, 5)public static String lastThree(String s) {
return s.substring(s.length() - 3); // last 3 characters
}Watch the boundary.
endis exclusive — this trips people up constantly."hello".substring(0, 3)gives"hel", not"hell".
indexOf finds the position of the first occurrence of a character or
substring; lastIndexOf finds the last occurrence. Both return -1 if
there's no match.
When to use it: locating where something is inside a string — e.g. the
position of a file extension's ., or checking that a character exists at
all (indexOf(x) >= 0).
"report.final.pdf".indexOf('.'); // 6 (the first dot)
"report.final.pdf".lastIndexOf('.'); // 12 (the last dot)public static String extension(String filename) {
int dot = filename.lastIndexOf('.');
if (dot == -1) {
return "";
}
return filename.substring(dot + 1);
}Returns true if the substring appears anywhere in the string.
When to use it: a simple "does this text show up somewhere in there?" check, when you don't care where.
"Hello World".contains("World"); // truepublic static boolean containsWord(String text, String word) {
return text.toLowerCase().contains(word.toLowerCase()); // case-insensitive
}Check whether the string begins (or ends) with a specific piece of text.
When to use it: validating a format — URLs, filenames, command prefixes — where the position of the match matters, not just its presence.
"https://example.com".startsWith("https://"); // truepublic static boolean isSecure(String url) {
return url.startsWith("https://");
}"weekend".endsWith("end"); // trueReturn a new copy of the string in all-uppercase or all-lowercase.
When to use it: normalizing text before comparing it, or literally displaying "shouted" or "quiet" text.
"hello".toUpperCase(); // "HELLO"public static boolean containsWord(String text, String word) {
return text.toLowerCase().contains(word.toLowerCase());
}Removes plain ASCII whitespace (spaces, tabs, newlines) from the beginning and end of the string. Never touches the middle.
When to use it: cleaning up user-typed input before you use it — form fields, command-line arguments, file lines.
" hello ".trim(); // "hello"public static boolean isBlank(String s) {
return s.trim().isEmpty();
}Does the same job as .trim(), but more correctly — it understands Unicode
whitespace (like non-breaking spaces), not just the handful of ASCII
characters .trim() knows about. There are also .stripLeading() and
.stripTrailing() for one side only.
When to use it: the same situations as .trim(). If you're on Java 11+,
prefer .strip() — it's the more robust choice.
" hello ".strip(); // "hello"public static String clean(String s) {
return s.strip();
}.isEmpty() checks for a zero-length string (""). .isBlank() checks for
a string that's empty or contains only whitespace (like " ") — usually
the more useful check.
When to use it: validating that a user actually typed something meaningful, not just spaces.
"".isEmpty(); // true
" ".isEmpty(); // false — it does have characters, they're just spaces
" ".isBlank(); // truepublic static boolean isBlank(String s) {
return s.isBlank(); // Java 11+, equivalent to s.trim().isEmpty()
}Breaks a string into an array of pieces, using the given text as the
separator. Important detail: the separator is treated as a regular
expression, not a literal string — so characters with special regex
meaning (like .) need to be escaped with \\.
When to use it: breaking up delimited data — CSV fields, path segments, words in a sentence.
"a,b,c".split(","); // ["a", "b", "c"]public static String[] parts(String version) {
return version.split("\\."); // escaped, because "." means "any character" in regex
}The reverse of split() — glues a collection of strings back together with
a delimiter between each pair, and no delimiter at the very start or end.
When to use it: building a delimited string from a list — a CSV row, a comma-separated summary, a file path.
java.util.List<String> words = java.util.List.of("a", "b", "c");
String.join(", ", words); // "a, b, c"public static String joinWords(java.util.List<String> words) {
return String.join(",", words);
}Strings are immutable, which is great for safety but can be clumsy when
you're assembling a string piece by piece (say, in a loop). StringBuilder
is a companion class that is mutable — you build it up in place, then
convert it back to a String at the end with .toString().
append() adds text to the end of the builder and returns the same builder
(so calls can be chained). .toString() converts the finished result back
into a regular String.
When to use it: anywhere you're assembling a string from many pieces, especially inside a loop.
StringBuilder sb = new StringBuilder();
sb.append("Hello").append(", ").append("world!");
sb.toString(); // "Hello, world!"public static String joinWords(java.util.List<String> words) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < words.size(); i++) {
sb.append(words.get(i));
if (i < words.size() - 1) {
sb.append(",");
}
}
return sb.toString();
}Reverses the characters currently in the builder, in place.
When to use it: reversing text, or checking palindromes.
new StringBuilder("hello").reverse().toString(); // "olleh"public static boolean isPalindrome(String s) {
String reversed = new StringBuilder(s).reverse().toString();
return s.equals(reversed);
}Removes the character at the given index, shifting everything after it left by one.
When to use it: editing text in place — removing a single unwanted character.
new StringBuilder("hello").deleteCharAt(1).toString(); // "hllo"public static String removeAt(String s, int index) {
StringBuilder sb = new StringBuilder(s);
sb.deleteCharAt(index);
return sb.toString();
}Remember to call
.toString()at the end!StringBuildermethods return aStringBuilder, not aString— a very common bug is returning the original unreversed/unmodifiedStringvariable instead of the builder's result.
Builds a string from a template with placeholders — %s for any value
(calls .toString() on it), %d for integers, and width/padding modifiers
like %05d (pad with zeros to 5 digits).
When to use it: building any string with multiple pieces of data plugged into fixed positions — messages, reports, labels.
String.format("%s is %d years old.", "Ana", 30); // "Ana is 30 years old."String.format("%05d", 42); // "00042"Specifiers are matched by position — the first
%matches the first argument, and so on. Putting%dwhere aStringargument lands throws an exception at runtime.
Returns the string concatenated with itself n times.
When to use it: building separators, padding, or ASCII art — anything that's the same text over and over.
"-".repeat(4); // "----"public static String divider(int width) {
return "=".repeat(width);
}Safely converts any value — including null — to its string form.
String.valueOf(null-reference) returns the literal text "null" instead of
throwing, unlike calling .toString() directly on a null reference.
When to use it: converting a value you're not 100% sure is non-null, or
converting a non-String type (like an int) to a String.
String.valueOf(42); // "42"
String.valueOf((Object) null); // "null" — no exceptionpublic static String describe(Object obj) {
return String.valueOf(obj); // safe even if obj is null
}That's the toolkit. Head over to java-strings-exercises.md and put it to
work — each exercise shows you a snippet using one or more of these methods
and asks you to decide whether it's doing the right thing. Some are correct;
some have a bug hiding in exactly the kind of detail covered above (an
off-by-one substring, a forgotten reassignment, an unescaped . in a
split() call). Reading the code carefully — the same way you'd review a
teammate's or an AI agent's pull request — is the actual skill being built
here.