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, aConnection). - Don't include secrets. Passwords, tokens, PII — leave them out, or mask them.
toStringends 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
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
Which of these is the best `toString` for a `Point(int x, int y)` class?