skip to content

How should you write a good toString() override, and what should it contain?

level: middleimportance: should knowfreq 55%

answer

  1. include the interesting state
  2. ClassName[field=value, ...] shape
  3. cheap, side-effect-free
  4. never leak secrets to logs
  5. don't make callers parse it — give accessors

basics

~10 s

Override toString() to return a short, readable string with the object's important fields, for example "Point[x=3, y=4]". Keep it concise, include enough to identify the object, and avoid dumping huge or secret data.

solid answer

~40 s

A good toString() returns a concise, human-readable description containing the fields that matter for identifying and debugging the object. A common format is the class name plus key fields, e.g. PhoneNumber[areaCode=707, number=867-5309]. Effective Java advises returning all the interesting information so logs and error messages are self-explanatory, and choosing a clear, documented format. Keep it cheap to compute and side-effect-free since it is called from logging and debuggers. Do not include sensitive data such as passwords or full card numbers — toString output often lands in logs. Don't rely on a specific layout programmatically; if callers need to parse data, expose real accessors instead. In practice most teams generate it (IDE, Lombok @ToString) or use records, which produce a sensible toString automatically.

code

java · 17 lines
java
public final class User {
    private final long id;
    private final String email;
    private final String passwordHash; // secret

    public User(long id, String email, String passwordHash) {
        this.id = id;
        this.email = email;
        this.passwordHash = passwordHash;
    }

    @Override
    public String toString() {
        // include identifying state; deliberately omit the secret
        return "User[id=" + id + ", email=" + email + "]";
    }
}

go deeper

for a junior

Can write a basic override that concatenates the class name and a few fields into a readable string.

for a middle

Knows the ClassName[field=value] convention, keeps it cheap/side-effect-free, and handles null fields safely.

for a senior

Cites Effective Java Item 12, decides whether to document/commit to the format vs provide accessors, and avoids leaking sensitive data and recursion.

for a principal

Sets team conventions (records/Lombok vs hand-rolled), treats toString as an observability and security surface (PII/secret hygiene in logs), and considers format stability as an implicit API contract across services.

## Why override it The inherited `Object.toString()` returns `ClassName@hexHash`, which hides all the object's data. Overriding it makes logs, exception messages, and debugger views readable. **Item 12 of *Effective Java*** states: *always override `toString()`* (in classes meant to be used and inspected), because the method is invoked automatically and a good implementation "makes the class much more pleasant to use and makes systems using the class easier to debug." ## What a good toString() contains - **The interesting state.** Include the fields a human would want when reading a log line — ideally enough to fully and unambiguously identify the instance. For a small value class, that is usually *all* the fields. - **A clear, concise format.** A widely used shape is `ClassName[field=value, field=value]`. Records produce exactly this style. - **Documentation of the format (or explicit non-commitment).** Decide whether the exact format is part of your API. If you document it, callers may depend on it — and you are then locked in. If you do **not** want that coupling, say so, and provide proper accessor methods for any data callers need programmatically. **Never** make people parse your `toString()` to get at data. ## Practical rules 1. **Keep it cheap and side-effect-free.** It runs in hot logging paths and in debuggers, so avoid heavy computation, I/O, or anything that mutates state. 2. **Avoid recursion/cycles.** If object A's `toString()` prints B and B prints A, you get infinite recursion / `StackOverflowError`. Print IDs instead of whole related objects. 3. **Beware huge output.** Don't dump entire large collections or byte arrays; summarize (e.g. size) instead. 4. **Do not leak secrets.** `toString()` output frequently ends up in logs, exception traces, and bug reports. Exclude passwords, tokens, full PANs, etc. 5. **Handle nulls gracefully.** Use `String.valueOf(field)` or `Objects.toString(field)` to render a possibly-null field as `"null"` instead of risking an NPE. ## How to produce it - **By hand**, using a `StringBuilder` or a text block / formatted string. - **IDE generation** (IntelliJ/Eclipse generate-toString). - **Lombok `@ToString`** (annotation-driven). - **Records** — since Java 16 a record auto-generates `Component[...]`; you rarely write one. ## Example ```java public final class Money { private final long cents; private final String currency; // ... @Override public String toString() { return "Money[amount=%d.%02d, currency=%s]" .formatted(cents / 100, Math.abs(cents % 100), currency); } } ``` This is concise, includes the meaningful state, and is safe to drop into a log line.

  • Should you document the exact format your toString() produces?
    Only if you intend to commit to it as API. If you document it, callers may depend on it and you can't change it freely. Often it's better to leave the format unspecified and provide real accessors for programmatic needs.
  • How do you avoid an NPE when a field used in toString() might be null?
    Use String.valueOf(field) or Objects.toString(field), which render null as the string "null" instead of throwing.

saying these in an interview costs you the question

  • Dumping passwords, tokens, or full card numbers in toString()
  • Encouraging callers to parse toString() instead of exposing accessors
  • Letting toString() recurse through related objects and overflow the stack
  • Doing I/O or mutating state inside toString()

context