String, StringBuilder, Character and regular expressions
String methods and immutability, mutable text with StringBuilder, the Character class, and validation, split and replaceAll with regex. A topic full of short output questions.
Text processing is in every program: validating a username, splitting a CSV line, counting words. In the exam this topic comes as short but tricky output questions: the end index of substring, the immutability of String, char arithmetic, the empty parts of split. There is also a “write a method” question: palindrome, word counting, converting to upper case.
Golden rules:
- String is immutable. Methods return a new String; if you do not assign it, the result is lost.
substring(a, b)does not take b. The length isb - a.- char is a number.
'A' + 2gives an int (67); if you want a character, cast it. - StringBuilder for text that changes. Its methods change the same object.
- A backslash in a regex is written twice in a Java String:
"\\d+".
String methods
String s = "Object Oriented";
System.out.println(s.length());
System.out.println(s.charAt(7));
System.out.println(s.indexOf('e'));
System.out.println(s.lastIndexOf('e'));
System.out.println(s.indexOf("Or"));
System.out.println(s.indexOf('z'));
System.out.println(s.substring(7));
System.out.println(s.substring(0, 6));
System.out.println(s.substring(2, 4));
System.out.println(s.toUpperCase() + " " + s.toLowerCase());
System.out.println("[" + " hi there ".trim() + "]");
System.out.println(s.replace('e', '3'));
System.out.println(s.startsWith("Obj") + " " + s.endsWith("ED") + " " + s.contains("ted"));
15
O
3
13
7
-1
Oriented
Object
je
OBJECT ORIENTED object oriented
[hi there]
Obj3ct Ori3nt3d
true false true
index: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
character: O b j e c t _ O r i e n t e d (_ = space)
substring(2, 4) → 2 and 3 → "je" substring(7) → from 7 to the end → "Oriented"
| Method | Returns | Note |
|---|---|---|
length() | int | In an array length is a field, in String it is a method |
charAt(i) | char | StringIndexOutOfBoundsException if i >= length() |
indexOf(x) / lastIndexOf(x) | int | -1 if not found |
substring(a, b) | String | a included, b excluded |
equals, equalsIgnoreCase | boolean | Content comparison |
compareTo | int | Dictionary order, code difference of the first differing character |
toUpperCase, toLowerCase, trim | String | A new String |
replace(a, b) | String | Plain text replacement, all occurrences |
split(regex) | String[] | Splits into parts |
toCharArray() | char[] | Array of characters |
String.valueOf(x) | String | Converts a number to text |
PITFALL · bounds.
"java".substring(2, 5)and"java".charAt(4)throwStringIndexOutOfBoundsExceptionwhen they run. But"java".substring(4)does not give an error, it returns an empty String (isEmpty()is true).
Immutability
Once a String object is created, its content does not change. Methods such as toUpperCase, concat and replace produce a new String.
String t = "java";
t.toUpperCase(); // result not assigned, lost
t.concat("!"); // same
System.out.println(t);
t = t.toUpperCase(); // t now points to the new object
System.out.println(t);
java
JAVA
For the same reason, after String u = "A"; String v = u; v += "B"; u is still "A": += creates a new object and assigns it to v. With StringBuilder, however, two references change the same object (below).
Comparison
System.out.println("Java".equals("java") + " " + "Java".equalsIgnoreCase("java"));
System.out.println("Apple".compareTo("apple"));
System.out.println("cat".compareTo("car"));
System.out.println("ab".compareTo("abcd"));
false true
-32
2
-2
The Character class and char arithmetic
A char is really a 16-bit number (a Unicode code). When it enters arithmetic it is promoted to int. Memorize: '0' = 48, 'A' = 65, 'a' = 97; the gap between upper and lower case is 32.
System.out.println('A' + 2); // int
System.out.println((char) ('A' + 2)); // char
System.out.println("A" + 2); // String concatenation
char c = 'x';
c++; // ++ stays a char
System.out.println(c);
System.out.println('7' - '0'); // value of the digit
67
C
A2
y
7
The static methods of the Character class:
| Method | Example | Result |
|---|---|---|
isLetter(c) | isLetter('B') | true |
isDigit(c) | isDigit('7') | true |
isLetterOrDigit(c) | isLetterOrDigit('_') | false |
isUpperCase(c) / isLowerCase(c) | isUpperCase('b') | false |
isWhitespace(c) | isWhitespace(' ') | true |
toUpperCase(c) / toLowerCase(c) | toUpperCase('q') | 'Q' |
getNumericValue(c) | getNumericValue('8') | 8 |
String mix = "a1B2 c3!";
int letters = 0, digits = 0, upper = 0, spaces = 0;
for (int i = 0; i < mix.length(); i++) {
char ch = mix.charAt(i);
if (Character.isLetter(ch)) letters++;
if (Character.isDigit(ch)) digits++;
if (Character.isUpperCase(ch)) upper++;
if (Character.isWhitespace(ch)) spaces++;
}
System.out.println(letters + " " + digits + " " + upper + " " + spaces);
3 3 1 1
StringBuilder
If you build text piece by piece or change it often, use StringBuilder. Its content is changeable (mutable); its methods change the same object and return it, which is why they are chained.
StringBuilder sb = new StringBuilder("code");
sb.append(42).append('!');
System.out.println(sb);
sb.insert(0, "my ");
System.out.println(sb);
sb.reverse();
System.out.println(sb);
sb.reverse().deleteCharAt(sb.length() - 1).setCharAt(0, 'M');
System.out.println(sb + " len=" + sb.length());
StringBuilder sb2 = new StringBuilder("abc");
sb2.replace(1, 2, "XYZ"); // 1 included, 2 excluded
System.out.println(sb2);
sb2.delete(0, 2);
System.out.println(sb2);
code42!
my code42!
!24edoc ym
My code42 len=9
aXYZc
YZc
| String | StringBuilder |
|---|---|
| Immutable | Mutable |
s = s + x a new object each time | sb.append(x) the same object |
equals compares content | equals is not overridden: address |
No reverse | Has reverse() |
Created with a literal: "abc" | new StringBuilder("abc") |
StringBuilder x = new StringBuilder("hi"), y = new StringBuilder("hi");
System.out.println(x.equals(y));
System.out.println(x.toString().equals(y.toString()));
StringBuilder p = new StringBuilder("A");
StringBuilder q = p;
q.append("B"); // same object
System.out.println(p);
false
true
AB
PITFALL · a loop that prepends.
r = i + r;andrb.insert(0, i);both put each element at the front. In a loop from 0 to 3, both produce3210. If you had appended at the end, it would be0123.
Regular expressions
A regex is a small language that describes a text pattern. In Java you use it in three places: matches (validation), split (splitting), replaceAll (replacing).
| Pattern | Meaning | Example match |
|---|---|---|
. | any character | a, 7, ! |
\d / \D | digit / non-digit | 5 |
\w | letter, digit or _ | x, _ |
\s | whitespace character (space, tab, newline) | " " |
[abc] | a, b or c | b |
[a-z], [A-Z0-9] | range | k, Q, 4 |
[^0-9] | not a digit | x |
X* | X zero or more times | "", XXX |
X+ | X one or more times | X, XX |
X? | X may or may not be there | colou?r → color, colour |
X{n}, X{n,}, X{n,m} | exactly n, at least n, between n and m | \d{9} |
Because \ is the escape character in a Java String, every backslash of the regex is written twice: \d+ in a regex, "\\d+" in Java. To search for a special character such as the dot literally, you also escape it: "\\.".
matches: validation
matches tells whether the whole text fits the pattern.
System.out.println("202312345".matches("\\d{9}")); // 9-digit student number
System.out.println("20231234".matches("\\d{9}"));
System.out.println("abc123".matches("[a-z]+\\d+"));
System.out.println("abc123x".matches("[a-z]+\\d+"));
System.out.println("Ankara".matches("[A-Z][a-z]*")); // a word starting with a capital letter
System.out.println("ali@bau.edu.tr".matches("[\\w.]+@[\\w.]+\\.[a-z]{2,}"));
System.out.println("05321234567".matches("05\\d{9}")); // mobile phone
true
false
true
false
true
true
true
split and replaceAll
System.out.println(Arrays.toString("a,b,,c,,".split(",")));
System.out.println("a,b,,c,,".split(",").length);
System.out.println(Arrays.toString(",x,y".split(",")));
System.out.println(Arrays.toString(" one two three ".trim().split("\\s+")));
System.out.println(Arrays.toString("1.5.9".split("\\.")));
System.out.println("1.5.9".split(".").length); // "." is any character: every character is a separator
[a, b, , c]
4
[, x, y]
[one, two, three]
[1, 5, 9]
0
System.out.println("programming".replaceAll("[aeiou]", "*"));
System.out.println("a1b22c333".replaceAll("\\d+", "#"));
System.out.println("too many spaces".replaceAll(" +", " "));
System.out.println("2026-10-28".replaceAll("(\\d+)-(\\d+)-(\\d+)", "$3/$2/$1"));
System.out.println("x.y.z".replace(".", "-") + " " + "x.y.z".replaceAll(".", "-"));
pr*gr*mm*ng
a#b#c#
too many spaces
28/10/2026
x-y-z -----
Parentheses create a group; in the replacement text you can refer back to the groups by the numbers 1, 2, 3 (the date example above).
Pattern and Matcher
To find, one by one, all the parts inside a text that fit the pattern:
Matcher m = Pattern.compile("\\d+").matcher("Room 12, floor 3, gate 104");
int sum = 0;
while (m.find()) {
System.out.print(m.group() + " ");
sum += Integer.parseInt(m.group());
}
System.out.println("sum=" + sum);
12 3 104 sum=119
matches wants the whole text, while find searches inside the text for the next part that fits.
A solved writing question: palindrome
“A man, a plan, a canal: Panama”: after throwing away everything except letters and digits and converting to lower case, is it the same as its reverse?
static boolean isPalindrome(String s) {
StringBuilder clean = new StringBuilder();
for (char c : s.toCharArray())
if (Character.isLetterOrDigit(c)) clean.append(Character.toLowerCase(c));
String fwd = clean.toString(); // copy BEFORE reverse
return fwd.equals(clean.reverse().toString());
}
System.out.println(isPalindrome("A man, a plan, a canal: Panama"));
System.out.println(isPalindrome("Java"));
true
false
Taking fwd before reverse is a must: reverse reverses the same object. clean.toString().equals(clean.reverse().toString()) also works (the left side is evaluated first), but a separate variable is more readable.
Common mistakes
| Wrong idea | The truth |
|---|---|
s.toUpperCase(); changes s | It returns a new String; s = s.toUpperCase(); |
substring(2, 4) is 3 characters | b is excluded: 2 characters |
s.length | In String it is a method: s.length() |
'A' + 2 → C | 67; for a character (char) ('A' + 2) |
sb1.equals(sb2) compares content | In StringBuilder it compares addresses; compare with toString() |
split(".") splits at the dot | In a regex the dot is any character; split("\\.") |
| split keeps trailing empty parts | It drops them; it keeps leading ones |
matches("\\d+") asks whether there is a digit in the text | It asks whether the whole text is digits. To search inside, Matcher.find |
s.charAt(0) = 'b'; | Does not compile; String is immutable |
s += x in a loop is no problem | A new object at each step; use StringBuilder |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
What is printed?
String s = "Object Oriented";
System.out.println(s.indexOf('e') + " " + s.lastIndexOf('e'));
System.out.println(s.substring(2, 4) + s.substring(7, 8));Show answer
Indexes start at 0: O0 b1 j2 e3 c4 t5 (space)6 O7 r8 i9 e10 n11 t12 e13 d14. The first 'e' is at 3, the last 'e' at 13. substring(2, 4) takes 2 and 3 (end excluded): "je". substring(7, 8) takes only 7: "O".
What is printed?
String t = "java";
t.toUpperCase();
t.concat("!");
System.out.println(t);Show answer
String is immutable. toUpperCase and concat return a new String; they do not change the object t points to. The returned value is lost because it is not assigned anywhere. The correct way is t = t.toUpperCase();.
What is printed?
System.out.println('A' + 2);
System.out.println((char) ('A' + 2));
System.out.println("A" + 2);
System.out.println('7' - '0');Show answer
char is a numeric type: 'A' + 2 is an int addition, 65 + 2 = 67. If you cast the result to char, it is the character number 67: C. "A" is a String, so + concatenates: A2. Subtracting '0' from a digit character gives the value of the digit: 55 − 48 = 7.
What is printed?
StringBuilder sb = new StringBuilder("code");
sb.append(42).append('!');
sb.insert(0, "my ");
System.out.println(sb);
sb.reverse();
System.out.println(sb);Show answer
StringBuilder is mutable; its methods change the same object and return it (so they can be chained). "code" → "code42" → "code42!" → insert "my " at the start: "my code42!". reverse reverses the characters: "!24edoc ym".
What is printed?
StringBuilder x = new StringBuilder("hi"), y = new StringBuilder("hi");
System.out.println(x.equals(y));
System.out.println(x.toString().equals(y.toString()));Show answer
StringBuilder does not override equals; Object's equals compares addresses, two separate objects: false. To compare the content, convert to String first: true. (Since Java 11, x.compareTo(y) == 0 also works.)
What is printed?
System.out.println(Arrays.toString("a,b,,c,,".split(",")));
System.out.println(Arrays.toString(",x,y".split(",")));Show answer
Two commas in a row produce an empty String: a, b, "", c. Empty parts at the end are dropped by split, so the array has 4 elements. An empty part at the start is not dropped: the first element is "".
What is printed?
System.out.println("202312345".matches("\\d{9}"));
System.out.println("20231234".matches("\\d{9}"));
System.out.println("abc123x".matches("[a-z]+\\d+"));Show answer
matches requires the whole String to fit the pattern. \\d{9} is exactly 9 digits: the first has 9 digits, the second has 8. [a-z]+\\d+ ends with letters followed by digits; the x at the end does not fit. In a Java String, the regex \d is written with two backslashes, "\\d".
What is printed?
System.out.println("programming".replaceAll("[aeiou]", "*"));
System.out.println("a1b22c333".replaceAll("\\d+", "#"));
System.out.println("x.y.z".replace(".", "-") + " " + "x.y.z".replaceAll(".", "-"));Show answer
[aeiou] is any vowel: o, a, i become stars. \\d+ is one or more consecutive digits, and each group becomes a single #. replace changes plain text, while replaceAll takes a regex: in a regex . means any character, so all five characters become dashes.
What is printed?
String mix = "a1B2 c3!";
int letters = 0, digits = 0, upper = 0;
for (int i = 0; i < mix.length(); i++) {
char ch = mix.charAt(i);
if (Character.isLetter(ch)) letters++;
if (Character.isDigit(ch)) digits++;
if (Character.isUpperCase(ch)) upper++;
}
System.out.println(letters + " " + digits + " " + upper);Show answer
Letters: a, B, c (3). Digits: 1, 2, 3 (3). Upper case: B (1). The space and ! do not count in any counter.
What is printed?
System.out.println("Apple".compareTo("apple"));
System.out.println("cat".compareTo("car"));
System.out.println("ab".compareTo("abcd"));Show answer
The code difference of the first differing character is returned. 'A' (65) − 'a' (97) = −32: upper case letters come before lower case ones. "cat"/"car": the first difference is t (116) − r (114) = 2. "ab" is the same as the beginning of "abcd"; if there is no difference, the length difference is returned: 2 − 4 = −2.
Find the compile error(s).
String s = "cat";
s.charAt(0) = 'b';
StringBuilder sb = "hello";
char c = s.substring(0, 1);Show answer
charAt returns a value, not a variable: javac unexpected type; required: variable, found: value. A letter of a String cannot be changed; use s = "b" + s.substring(1); or StringBuilder's setCharAt. A literal cannot be assigned to a StringBuilder: new StringBuilder("hello"). substring always returns a String; for a char use s.charAt(0).
Write a method capitalizeWords(String text) that returns the words of text separated by single spaces, each with its first letter upper case and the rest lower case. capitalizeWords(" hello wORLD from java ") returns Hello World From Java.
Show answer
First remove the spaces at the start and end, then split on one or more spaces. Use StringBuilder instead of concatenating Strings in a loop:
public static String capitalizeWords(String text) {
String[] words = text.trim().split("\\s+");
StringBuilder sb = new StringBuilder();
for (String w : words) {
if (sb.length() > 0) sb.append(' ');
sb.append(Character.toUpperCase(w.charAt(0)));
sb.append(w.substring(1).toLowerCase());
}
return sb.toString();
}Without trim, split would produce an empty String at the start and w.charAt(0) would throw StringIndexOutOfBoundsException.Verification: All code examples and answers were run with JDK 26 (java verify/SEN1002/W08.java, W08b.java); the compile errors were tried with javac (verify/SEN1002/err/E08a-c.java).