W3docs

Java toString() Method

Override toString() in Java classes to produce useful string representations for logging and debugging.

toString is the method System.out.println, string concatenation, and your debugger all reach for when they need to render an object as text. The default — inherited from Object — is the class name plus an unreadable hex hash. Overriding toString is one of the cheapest, highest-payoff things you can do for the people who'll read your logs and stack traces (frequently a future version of you).

Where it's called from

You almost never call toString directly. It's invoked implicitly any time an object is turned into text:

Point p = new Point(3, 4);
System.out.println(p);          // calls p.toString()
String msg = "got " + p;        // calls p.toString()
log.info("point: {}", p);       // calls p.toString()

Anywhere an Object shows up in a context that wants a String, toString runs. That's why the default is so disappointing — it shows up everywhere and tells you nothing useful.

The default

Object.toString() returns getClass().getName() + "@" + Integer.toHexString(hashCode()). So a plain class prints as something like com.example.Point@1540e19d. The class is fine; the hex is the identity hash code, which is useless when you're trying to figure out what the object's fields contain.

A good override

A good toString is short, unambiguous, and contains the data a reader would want to see:

@Override
public String toString() {
  return "Point[x=" + x + ", y=" + y + "]";
}

A few conventions worth following:

  • Include the class name. When a log line mixes objects, knowing what kind it is matters.
  • Use a stable, parseable shape. Class[field=value, field=value] is what records and most modern Java libraries use. It's readable and grep-friendly.
  • Show the fields that matter — usually the ones in equals. Skip enormous fields (a 10MB blob, a Connection).
  • Don't include secrets. Passwords, tokens, PII — leave them out, or mask them. toString ends up in log files.

With String.format or text blocks

For more than three or four fields, String.format (or a text block) reads better than concatenation:

@Override
public String toString() {
  return String.format("User[id=%d, name=%s, role=%s]", id, name, role);
}

Don't make it expensive

toString is called more often than you'd think — log frameworks render arguments lazily but eagerly enough that an O(n²) toString on a 100k-element list will absolutely make your service slow. Keep the output bounded. A collection's toString should be limited to a sensible head, with ... (N more) for the rest.

Don't throw from toString

Throwing in toString turns a routine log line into a NullPointerException somewhere unexpected — and the underlying problem you were trying to log gets lost. Guard against nulls:

@Override
public String toString() {
  return "Order[id=" + id + ", customer=" + (customer == null ? "<none>" : customer.name()) + "]";
}

Records get one for free

If your class is a plain data carrier, records already generate a good toString:

record Point(int x, int y) {}
System.out.println(new Point(3, 4));   // Point[x=3, y=4]

So overriding toString is mostly something you do for non-record classes.

A worked example

java— editable, runs on the server

What's next

toString produces a description of an object. The next chapter is about producing a copy of one — clone, Cloneable, and the awkward design choices around shallow vs. deep copies. Continue to Java object cloning.

Practice

Practice

Which of these is the best `toString` for a `Point(int x, int y)` class?