Challenge

Break a String with reflection

20 minintermediate28 yrs

Edge cases. You have to reason, and two valid fixes differ.

What this teaches

  • final on a reference field protects the reference, not the referent
  • Immutability is an API property — private state that never leaks
  • String caches its hash, which is only sound because the value cannot change
  • A string with a stale hash is a map key that can never be found again
  • Mutating a pooled literal corrupts it for every class in the JVM

Starter

Starter.java
import java.lang.reflect.*;

/**
 * Challenge: prove that "String is immutable because the field is final" is
 * wrong, and work out what the real reason is.
 *
 * Run with:
 *   javac Starter.java -d /tmp/out
 *   java --add-opens java.base/java.lang=ALL-UNNAMED -cp /tmp/out Starter
 *
 * The --add-opens flag is required: reflecting into java.lang has been blocked
 * by default since JDK 16. That refusal is itself part of the answer.
 */
public class Starter {

    public static void main(String[] args) throws Exception {
        Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);

        System.out.println("declared as: " + Modifier.toString(value.getModifiers())
            + " " + value.getType().getSimpleName());

        String s = new String("hello".toCharArray());
        System.out.println("before: " + s);

        // TODO 1: mutate s in place through the array you just obtained, and
        // print it. The field is final. Does that stop you?

        // TODO 2: print s.hashCode() before and after your mutation. Explain
        // the result. Then work out what it means for using s as a HashMap key.

        // TODO 3: do the same to the literal "hello" instead of to s.
        // Then, on a later line, print the plain literal "hello" and see what
        // comes out. Why is this different from TODO 1?

        // TODO 4: in one sentence, say what actually makes String immutable.
        // "The field is final" is not the answer — you just disproved it.
    }
}

Run it locally:

cd exercises/java/strings/string-immutability-and-pool/02-challenge
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Hints

  1. Hint 1

    String.value is private final byte[]. setAccessible(true) gets you the array itself, not a copy — and final says nothing about the contents.

  2. Hint 2

    hashCode() is computed once and stored in a field. Read it before you mutate, and again after.

  3. Hint 3

    new String(char[]) copies. A literal does not — so mutating a literal's array changes it everywhere that literal appears.

Done when

  • You mutate a String in place and print the new value
  • You show hashCode() returning a value that no longer matches the content
  • You demonstrate that the same trick on a literal affects an unrelated line
  • A comment explains what actually makes String immutable, in one sentence

← Back to Why is String immutable, and what is the string pool?