skip to content

Where is toString() invoked automatically, and how is null handled in those paths?

level: middleimportance: should knowfreq 50%

answer

  1. concatenation -> String.valueOf -> toString
  2. println(obj) routes through String.valueOf
  3. String.valueOf(null) = "null", no NPE
  4. direct nullRef.toString() throws NPE
  5. invokedynamic / StringConcatFactory since Java 9

basics

~10 s

toString() runs automatically during string concatenation with +, in System.out.println(obj), and via String.valueOf(obj). Those paths handle a null reference by printing "null" instead of throwing.

solid answer

~40 s

You rarely call toString() directly; the language and library call it for you. String concatenation ("x=" + obj) compiles to operations that convert obj via String.valueOf, which calls toString(). System.out.println(obj) and print(obj) route through String.valueOf as well, as do logging frameworks and IDE debuggers. The key safety detail is that String.valueOf(null) returns the literal "null" rather than throwing a NullPointerException — so "name=" + null prints "name=null". By contrast, calling obj.toString() directly on a null reference throws NPE, since there's no object to dispatch to. Modern javac compiles + concatenation using invokedynamic and StringConcatFactory rather than chained StringBuilder, but the observable null-to-"null" behavior is unchanged. Because toString() fires implicitly, an exception thrown inside it can surface in surprising places like a log statement.

code

java · 8 lines
java
Object x = null;

System.out.println("x=" + x);          // prints: x=null   (via String.valueOf)
System.out.println(x);                 // prints: null     (println(Object) -> String.valueOf)
System.out.println(String.valueOf(x)); // prints: null

// Direct call on a null reference is NOT safe:
// x.toString();                       // throws NullPointerException

go deeper

for a junior

Knows println(obj) and "text" + obj automatically produce obj's string, and that null prints as "null" in those cases.

for a middle

Can name String.valueOf as the bridge and explain that direct nullRef.toString() throws while the concatenation/println path does not.

for a senior

Explains the invokedynamic/StringConcatFactory compilation of + and how implicit invocation (esp. logging) can surface toString() bugs in unexpected places.

for a principal

Reasons about performance of concatenation in hot paths, the observability/security implications of implicit toString in logs, and guards toString() to be cheap and exception-free as a cross-cutting policy.

## You usually don't call it yourself `toString()` is special because the language and standard library invoke it **implicitly**. Knowing exactly where helps you reason about both convenience and surprises. ## The invocation sites 1. **String concatenation with `+`.** When either operand of `+` is a `String`, the other operand is converted to a string. For a reference operand, the conversion goes through **`String.valueOf(Object)`**, which is: ```java public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); } ``` So `"p=" + p` effectively does `"p=" + String.valueOf(p)`. 2. **`System.out.println(obj)` / `print(obj)`.** `PrintStream.println(Object)` calls `String.valueOf(obj)` internally, then writes the result. 3. **`String.valueOf(obj)` and `Objects.toString(obj)`** — explicit but null-safe wrappers many libraries use. 4. **Logging frameworks** (SLF4J/Logback/Log4j) call `toString()` on message arguments when a log statement is actually emitted. 5. **Debuggers / IDEs** display an object's `toString()` in variable views. 6. **Collections** — a collection's own `toString()` (e.g. `List`, `Map`) calls `toString()` on each element, so printing a list prints all elements' representations. ## The null-handling rule (the part interviewers probe) - `String.valueOf((Object) null)` returns the **literal string `"null"`** — it does **not** throw. - Therefore `"name=" + nameRef` prints `name=null` when `nameRef` is null, and `System.out.println((Object) null)` prints `null`. - **But** `nameRef.toString()` called **directly** on a null reference throws a **`NullPointerException`**, because there is no object on which to dispatch the method. The safety only exists in the `String.valueOf`/concatenation/`println` paths, not in a direct call. ## Implementation note (modern javac) Since Java 9, the compiler typically translates `+` concatenation to an **`invokedynamic`** call bootstrapped by **`java.lang.invoke.StringConcatFactory`**, instead of the older pattern of allocating a `StringBuilder` and calling `append`. This is a performance/footprint change; the **observable behavior** — including converting references through `String.valueOf` and printing `"null"` for null — is the same. ## A practical hazard Because `toString()` runs implicitly (notably inside logging), a bug in `toString()` (an NPE, an infinite recursion, an expensive call) can blow up far from where the object was created — e.g. a logging line throws instead of the business logic. Keep `toString()` cheap and exception-free.

  • Does "value=" + someNullReference throw a NullPointerException?
    No. Concatenation routes the reference through String.valueOf, which returns "null" for a null argument, so you get "value=null".
  • Why can a buggy toString() crash a log statement specifically?
    Because logging frameworks call toString() on the arguments when emitting the line. If toString() throws or recurses infinitely, the failure surfaces at the log call, far from where the object was built.

saying these in an interview costs you the question

  • Claiming "x" + null throws a NullPointerException
  • Thinking a direct nullRef.toString() is null-safe like the concatenation path
  • Assuming + concatenation still always builds a StringBuilder under the hood
  • Forgetting that logging can trigger toString() and surface its exceptions

context