Free Handbook · Every example compiled & verified

Strings & Arrays

Java Strings and arrays: immutability, == vs equals, StringBuilder, text blocks, formatting, Arrays helpers, 2-D arrays and the index errors they cause.

0 / 142 lessons🔥 0 day streak
ShareXLinkedIn

Module 03 · what you'll be able to do

  • Explain why a Java String is immutable and why == on Strings is a bug
  • Use the everyday String methods and build text efficiently with StringBuilder
  • Format output with String.format, formatted and text blocks
  • Declare, fill, copy, sort and print arrays with the java.util.Arrays helpers
  • Work with 2-D arrays and read an ArrayIndexOutOfBoundsException without guessing
01

Strings are immutable objects

A String is an object that holds a sequence of characters. It is immutable: once created, its characters never change. Every method that looks like it modifies a String — toUpperCase(), replace(), trim() — actually returns a new String and leaves the original alone. If you do not store the result, the work is thrown away.

javaMain.java
public class Main {
    public static void main(String[] args) {
        String name = "asha";
        name.toUpperCase();                 // result ignored: name is unchanged
        System.out.println(name);

        String upper = name.toUpperCase();  // keep the new String
        System.out.println(upper);

        name = name + " khan";              // + builds a new String too
        System.out.println(name);
        System.out.println(name.length());
    }
}
Outputcompiled & run with real Java
asha
ASHA
asha khan
9
Your turn

Call name.replace("a", "@") without assigning it, print name, then assign it and print again.

Why immutable?
Immutability makes Strings safe to share. The JVM keeps one copy of each string literal in the string pool and hands the same object to every place that uses that literal; that only works if nobody can change it. It also makes Strings safe as HashMap keys and safe to pass between threads.
02

Common String methods

Characters are indexed from 0. length() is a method (with parentheses). substring(begin, end) includes begin and excludes end, so end - begin is the length of the result.

MethodReturnsExample → result
length()number of chars"Java".length() → 4
charAt(i)the char at index i"Java".charAt(0) → 'J'
indexOf(s)first index, or -1"banana".indexOf("an") → 1
substring(b, e)chars b to e-1"Hello".substring(1, 4) → "ell"
contains(s), startsWith, endsWithboolean"report.pdf".endsWith(".pdf") → true
toUpperCase(), toLowerCase()new String"Hi".toLowerCase() → "hi"
strip() / trim()without outer whitespace" x ".strip() → "x"
replace(a, b)every a replaced by b"a-b-c".replace("-", "/") → "a/b/c"
split(regex)String[]"a,b".split(",") → [a, b]
String.join(sep, ...)joined StringString.join("-", "a", "b") → "a-b"
isEmpty() / isBlank()boolean" ".isBlank() → true
repeat(n)String repeated n times"ab".repeat(3) → "ababab"
javaMain.java
public class Main {
    public static void main(String[] args) {
        String email = "  [email protected] ";
        String clean = email.strip().toLowerCase();
        System.out.println("[" + clean + "]");

        int at = clean.indexOf('@');
        String user = clean.substring(0, at);
        String domain = clean.substring(at + 1);
        System.out.println(user + " | " + domain);

        System.out.println(clean.endsWith(".com"));
        System.out.println(clean.contains("khan"));
        System.out.println(user.replace(".", " "));

        String csv = "red,green,blue";
        String[] parts = csv.split(",");
        System.out.println(parts.length + " parts, second = " + parts[1]);
        System.out.println(String.join(" / ", parts));
        System.out.println("-".repeat(10));
    }
}
Outputcompiled & run with real Java
[[email protected]]
asha.khan | example.com
true
true
asha khan
3 parts, second = green
red / green / blue
----------
Your turn

Print the file extension of "archive.tar.gz" using lastIndexOf('.') and substring.

split takes a regular expression
"a.b.c".split(".") returns an empty array, because . in a regex means "any character". Escape it: split("\\."). The same goes for |, +, * and ?.
Error you will hit

StringIndexOutOfBoundsException — charAt past the end

java
public class Main {
    public static void main(String[] args) {
        String word = "Java";
        System.out.println(word.charAt(4));
    }
}
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: Index 4 out of bounds for length 4
	at java.base/jdk.internal.util.Preconditions$1.apply(Unknown Source)
	...
	at java.base/java.lang.String.charAt(Unknown Source)
	at Main.main(Main.java:4)
Why the compiler said that

"Java" has 4 characters at indexes 0, 1, 2 and 3. Index 4 does not exist. The message tells you both numbers — the index you asked for and the length — which is usually enough to spot the off-by-one. Skip the java.base frames and read down to the first line in your own file.

The fix

The last valid index is length() - 1.

java
String word = "Java";
System.out.println(word.charAt(word.length() - 1));   // a
03

== vs equals: comparing Strings

For objects, == asks "are these the same object in memory?" equals() asks "do these hold the same characters?" Two Strings with identical text can be different objects, so == can say false for text that looks equal. Always compare String content with equals (or equalsIgnoreCase).

javaMain.java
public class Main {
    public static void main(String[] args) {
        String a = "hello";
        String b = "hello";                 // same literal: same pooled object
        String c = new String("hello");     // forced new object
        String d = "hel" + "lo".toString(); // built at runtime: new object

        System.out.println(a == b);
        System.out.println(a == c);
        System.out.println(a == d);
        System.out.println(a.equals(c));
        System.out.println(a.equals(d));
        System.out.println("HELLO".equalsIgnoreCase(a));
        System.out.println("apple".compareTo("banana") < 0);
    }
}
Outputcompiled & run with real Java
true
false
false
true
true
true
true

a == b is true only because both literals point at the same pooled object. That is an implementation detail, not something to rely on.

Null-safe comparison
If a variable might be null, put the literal first: "admin".equals(role) returns false for a null role, while role.equals("admin") throws a NullPointerException. java.util.Objects.equals(x, y) is null-safe on both sides.
Quick check

A user types yes and it is read into String answer with Scanner. What does answer == "yes" most likely give?

04

StringBuilder: building text in a loop

Because Strings are immutable, s = s + x inside a loop creates a brand-new String on every pass and copies all the old characters into it. For 10 items that does not matter; for 100,000 it is quadratic. StringBuilder is a mutable buffer: append adds to the same object, and toString() produces the final String once.

javaMain.java
public class Main {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        for (int i = 1; i <= 5; i++) {
            sb.append(i);
            if (i < 5) sb.append(", ");
        }
        System.out.println(sb.toString());

        StringBuilder word = new StringBuilder("stressed");
        System.out.println(word.reverse());     // mutates word itself

        StringBuilder path = new StringBuilder("/home/user");
        path.insert(0, "C:").append("/docs");
        path.setCharAt(2, '\\');
        System.out.println(path + " (" + path.length() + " chars)");
    }
}
Outputcompiled & run with real Java
1, 2, 3, 4, 5
desserts
C:\home/user/docs (17 chars)
Your turn

Build the string *-*-*-*-* with a loop and a StringBuilder, then do it again with String.join.

In real jobs
A single expression like "Hi " + name + "!" is fine — the compiler optimises it. The rule is only about loops: concatenating in a loop is a classic code-review comment and a real cause of slow report and CSV generation. StringBuilder is not thread-safe; its older sibling StringBuffer is synchronized and almost never needed.
Error you will hit

String cannot be converted to StringBuilder

java
public class Main {
    public static void main(String[] args) {
        StringBuilder sb = "start";
        sb.append("!");
        System.out.println(sb);
    }
}
Main.java:3: error: incompatible types: String cannot be converted to StringBuilder
        StringBuilder sb = "start";
                           ^
1 error
error: compilation failed
Why the compiler said that

A string literal is a String. StringBuilder is a different class, and Java never converts between them for you.

The fix

Create the builder with its constructor, passing the starting text.

java
StringBuilder sb = new StringBuilder("start");
05

Formatting and text blocks

String.format(pattern, values...) fills placeholders in a template; "pattern".formatted(values...) (Java 15+) does the same thing called on the template. System.out.printf prints the formatted result directly. The common placeholders are %s (anything, as text), %d (whole number), %f (decimal, %.2f for two places), %n (newline) and width/alignment like %-10s (left-aligned in 10 columns) or %5d (right-aligned in 5).

javaMain.java
public class Main {
    public static void main(String[] args) {
        String name = "Asha";
        int items = 3;
        double total = 1499.5;

        String line = String.format("%s bought %d items for %.2f", name, items, total);
        System.out.println(line);
        System.out.println("%s: %05d".formatted("Order", 42));

        System.out.printf("%-8s|%6s%n", "Item", "Price");
        System.out.printf("%-8s|%6.1f%n", "Pen", 20.0);
        System.out.printf("%-8s|%6.1f%n", "Notebook", 145.5);
        System.out.println(String.format("%,d", 12500000));
    }
}
Outputcompiled & run with real Java
Asha bought 3 items for 1499.50
Order: 00042
Item    | Price
Pen     |  20.0
Notebook| 145.5
12,500,000

Text blocks (Java 15+)

A text block is a multi-line string between """ delimiters. The opening """ must be followed by a newline. Java strips the common leading indentation, so you can indent the block with your code. Perfect for JSON, SQL and HTML snippets that used to need \n and + on every line.

javaMain.java
public class Main {
    public static void main(String[] args) {
        String sql = """
            SELECT id, name
            FROM customers
            WHERE city = '%s'
            """.formatted("Pune");
        System.out.print(sql);

        String json = """
            {"name": "Asha", "active": true}""";
        System.out.println(json);
    }
}
Outputcompiled & run with real Java
SELECT id, name
FROM customers
WHERE city = 'Pune'
{"name": "Asha", "active": true}

When the closing """ sits on its own line, the text ends with a newline — that is why the first block is printed with print, not println.

Never format user input into SQL
The SQL example above is for showing text blocks only. With real user input, formatted into SQL is an injection hole; use a PreparedStatement with ? parameters.
06

Arrays: declare, fill, iterate

An array is a fixed-size row of values of one type. You choose the size when you create it and it can never grow — that is what ArrayList (module 08) is for. Elements get default values (0, 0.0, false, null). The size is the field length — no parentheses, unlike String.length(). Indexes run from 0 to length - 1.

javaMain.java
public class Main {
    public static void main(String[] args) {
        int[] scores = new int[4];            // four zeros
        scores[0] = 90;
        scores[3] = 70;

        String[] days = {"Mon", "Tue", "Wed"}; // initialiser: size is 3
        double[] prices = new double[]{9.5, 20.0};

        System.out.println(scores.length + " " + days.length + " " + prices.length);
        System.out.println(scores[1] + " " + scores[3]);

        for (int i = 0; i < days.length; i++) {
            System.out.println(i + ": " + days[i]);
        }

        int sum = 0;
        for (int s : scores) {                // enhanced for: values only
            sum += s;
        }
        System.out.println("sum = " + sum);

        String[] names = new String[2];
        System.out.println(names[0]);         // default for objects is null
    }
}
Outputcompiled & run with real Java
4 3 2
0 70
0: Mon
1: Tue
2: Wed
sum = 160
null
Your turn

Find the largest value in {4, 19, 7, 12} with a loop. Start max at the first element, not at 0 — think about an array of negatives.

VisualizeFinding the maximum, one passStep 1 / 8
int[] a = {4, 19, 7};
int max = a[0];
for (int i = 1; i < a.length; i++) {
if (a[i] > max) max = a[i];
}
System.out.println(max);
Line 1

The array holds three ints.

Variables now
a[4, 19, 7]
All 8 steps as a table
StepLineWhat happenedVariables now
11The array holds three ints.a = [4, 19, 7]
22Start with the first element as the best so far.max = 4
33i = 1; 1 < 3 so enter the loop.i = 1
44a[1] is 19, bigger than 4: update.max = 19
53i = 2; 2 < 3, loop again.i = 2
64a[2] is 7, not bigger than 19: no change.max = 19
73i = 3; 3 < 3 is false, the loop ends.i = 3
86Print the result.
Error you will hit

ArrayIndexOutOfBoundsException — <= instead of <

java
public class Main {
    public static void main(String[] args) {
        int[] scores = {90, 75, 88};
        for (int i = 0; i <= scores.length; i++) {
            System.out.println(scores[i]);
        }
    }
}
90
75
88
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
	at Main.main(Main.java:5)
Why the compiler said that

The program compiles and even prints three values, then crashes on the fourth pass. With <= the loop runs for i = 3, but a length-3 array only has indexes 0, 1, 2. Java checks every array access at runtime, so you get an exception instead of reading random memory.

The fix

Loop with i < scores.length, or use an enhanced for when you do not need the index.

java
for (int i = 0; i < scores.length; i++) {
    System.out.println(scores[i]);
}
Error you will hit

cannot find symbol — length() on an array, length on a String

java
public class Main {
    public static void main(String[] args) {
        int[] scores = {90, 75, 88};
        String name = "Asha";
        System.out.println(scores.length() + name.length);
    }
}
Main.java:5: error: cannot find symbol
        System.out.println(scores.length() + name.length);
                                 ^
  symbol:   method length()
  location: variable scores of type int[]
Main.java:5: error: cannot find symbol
        System.out.println(scores.length() + name.length);
                                                 ^
  symbol:   variable length
  location: variable name of type String
2 errors
error: compilation failed
Why the compiler said that

Arrays have a field called length; String has a method called length(). The compiler looks for exactly what you wrote and finds neither. The symbol: and location: lines tell you what it searched for and where.

The fix

Array: scores.length. String: name.length(). (Collections use size().)

java
System.out.println(scores.length + name.length());
07

The Arrays helper class

Printing an array directly shows something like [I@1b6d3586 — the type code and a hash, not the contents. java.util.Arrays has static helpers for the jobs you do all the time: toString, sort, fill, copyOf, copyOfRange, equals and binarySearch (on a sorted array).

javaMain.java
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] nums = {5, 3, 9, 1};
        System.out.println(Arrays.toString(nums));

        int[] bigger = Arrays.copyOf(nums, 6);      // pads with zeros
        int[] middle = Arrays.copyOfRange(nums, 1, 3);
        System.out.println(Arrays.toString(bigger));
        System.out.println(Arrays.toString(middle));

        Arrays.sort(nums);                          // sorts in place
        System.out.println(Arrays.toString(nums));
        System.out.println("9 is at " + Arrays.binarySearch(nums, 9));

        char[] line = new char[5];
        Arrays.fill(line, '=');
        System.out.println(new String(line));

        int[] a = {1, 2}, b = {1, 2};
        System.out.println(a == b);                 // different objects
        System.out.println(Arrays.equals(a, b));    // same contents

        String[] words = {"pear", "Apple", "fig"};
        Arrays.sort(words);                         // uppercase sorts first
        System.out.println(Arrays.toString(words));
    }
}
Outputcompiled & run with real Java
[5, 3, 9, 1]
[5, 3, 9, 1, 0, 0]
[3, 9]
[1, 3, 5, 9]
9 is at 3
=====
false
true
[Apple, fig, pear]
Your turn

Sort words case-insensitively with Arrays.sort(words, String.CASE_INSENSITIVE_ORDER).

Assignment does not copy an array
int[] copy = nums; copies the reference: both names now point at the same array, and a change through one is visible through the other. Use Arrays.copyOf or nums.clone() for a real copy. Module 04 traces why this matters when you pass arrays to methods.
08

2-D arrays and char arithmetic

Java has no true matrix type. A 2-D array is an array of arrays: grid[r] is a whole row (itself an int[]) and grid[r][c] is one cell. grid.length is the number of rows and grid[r].length the length of row r. Rows can even have different lengths (a "jagged" array).

javaMain.java
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[][] grid = {
            {1, 2, 3},
            {4, 5, 6}
        };
        System.out.println(grid.length + " rows, " + grid[0].length + " cols");
        System.out.println(grid[1][2]);

        for (int r = 0; r < grid.length; r++) {
            for (int c = 0; c < grid[r].length; c++) {
                System.out.print(grid[r][c] * 10 + " ");
            }
            System.out.println();
        }

        int[][] jagged = new int[3][];
        for (int i = 0; i < jagged.length; i++) {
            jagged[i] = new int[i + 1];
            Arrays.fill(jagged[i], i + 1);
        }
        System.out.println(Arrays.deepToString(jagged));
    }
}
Outputcompiled & run with real Java
2 rows, 3 cols
6
10 20 30
40 50 60
[[1], [2, 2], [3, 3, 3]]
Your turn

Write the nested loop that prints the column sums of grid: 5 7 9.

Error you will hit

NullPointerException — a 2-D array whose rows were never created

java
public class Main {
    public static void main(String[] args) {
        int[][] grid = new int[3][];
        grid[0][0] = 1;
    }
}
Exception in thread "main" java.lang.NullPointerException: Cannot store to int array because "<local1>[0]" is null
	at Main.main(Main.java:4)
Why the compiler said that

new int[3][] creates the outer array of three row slots, but each slot is null until you put a row in it. The message says exactly that: row [0] is null. (<local1> is grid; the compiler did not keep local variable names, so the JVM uses the slot number.)

The fix

Give both sizes when every row has the same length, or create each row before using it.

java
int[][] grid = new int[3][4];   // 3 rows of 4 zeros
grid[0][0] = 1;

char is a number

A char is a 16-bit number (its Unicode code) that happens to print as a letter. So you can do arithmetic on it: 'a' + 1 is the int 98, and (char) ('a' + 1) is 'b'. c - '0' turns a digit character into its value, and c - 'a' gives a letter's position — the basis of every "count the letters" interview question.

javaMain.java
public class Main {
    public static void main(String[] args) {
        char c = 'a';
        System.out.println(c + 1);            // int arithmetic
        System.out.println((char) (c + 1));   // back to a char
        c++;                                  // ++ keeps the char type
        System.out.println(c);

        System.out.println('7' - '0');        // digit char -> int value

        int[] counts = new int[26];
        for (char ch : "banana".toCharArray()) {
            counts[ch - 'a']++;
        }
        for (int i = 0; i < 26; i++) {
            if (counts[i] > 0) System.out.println((char) ('a' + i) + "=" + counts[i]);
        }
        System.out.println(Character.isDigit('7') + " " + Character.toUpperCase('q'));
    }
}
Outputcompiled & run with real Java
98
b
b
7
a=3
b=1
n=2
true Q
Your turn

Write a Caesar shift: turn "abc" into "def" by adding 3 to each char.

Error you will hit

array required, but String found

java
public class Main {
    public static void main(String[] args) {
        String name = "Asha";
        char first = name[0];
        System.out.println(first);
    }
}
Main.java:4: error: array required, but String found
        char first = name[0];
                         ^
1 error
error: compilation failed
Why the compiler said that

Square brackets only work on arrays. A Java String is an object, not a char[], so it has no [] indexing (unlike Python or JavaScript).

The fix

Use charAt, or convert once with toCharArray() if you need to index many times.

java
char first = name.charAt(0);
Immutable
Cannot be changed after creation. Every "modifying" String method returns a new String.
String pool
The JVM's shared store of string literals; identical literals refer to the same object.
equals vs ==
equals compares content; == on objects compares whether two references point at the same object.
StringBuilder
A mutable character buffer for building Strings efficiently, especially in loops.
Text block
A multi-line String literal between """ delimiters (Java 15+), with incidental indentation stripped.
Array
A fixed-length, zero-indexed sequence of one type. Size in the length field.
Jagged array
A 2-D array whose rows have different lengths — possible because each row is a separate array.
ArrayIndexOutOfBoundsException
Thrown at runtime when an index is negative or not less than the array length.
Quick check

What does this print? int[] a = {1, 2, 3}; int[] b = a; b[0] = 99; System.out.println(a[0]);

Frequently asked questions

Why should I not use == to compare Strings in Java?
== compares object identity, not text. Two Strings with the same characters are often different objects (anything read from input, a file or built at runtime), so == returns false. Use equals(), or Objects.equals(a, b) when either side may be null.
How do I print an array in Java?
Use Arrays.toString(arr) for a 1-D array and Arrays.deepToString(grid) for a 2-D array. Printing the array variable directly shows its type code and hash (like [I@1b6d3586), not its contents.
When should I use StringBuilder instead of +?
Inside loops, or whenever you build a String piece by piece in many statements. A single expression such as "Hi " + name is fine; the compiler handles it efficiently. Repeated s += x in a loop copies the whole String each time.

Finish the Java handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.