toString() Method in Java: Override and Examples
You create a Person object, pass it to System.out.println(person), and instead of a name and age the console shows something like oop.Person@5e2a3193. Nothing is broken: this is the toString() method that the class inherited from Object and never overrode.
The toString() method in Java is a method of the Object class with the signature public String toString(). It returns a string representation of an object. What does toString() do in Java? It turns an object into readable text, and Java calls it automatically whenever an object has to be printed, concatenated with a string or written to a log. Every object has this method, because every class ultimately extends Object. When you write a new class, you usually override toString() so the returned string includes the class name and the values of its main fields.
What toString() returns by default
If a class doesn't override the method, Java calls the implementation from the Object class, which looks like this:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
} The result has three parts: the fully qualified class name (including the package), an @ character, and the object's hash code in hexadecimal. For a Person class in the oop package, you get a string like:
oop.Person@5e2a3193 This string tells you almost nothing about what the object contains. That's why the Javadoc for Object.toString() recommends that all subclasses override the method.
Good to know
The number after @ is not the object's memory address. It is the value of hashCode() in hexadecimal. The JVM can move objects around during garbage collection, but an object's hash code never changes.
Overriding the toString() method
To get meaningful output, declare a public String toString() method and mark it with @Override. A common format is ClassName{field=value, ...}. Here is a toString() example in Java:
public class Person {
private String fullName;
private int age;
private boolean retired;
public Person(String fullName, int age, boolean retired) {
this.fullName = fullName;
this.age = age;
this.retired = retired;
}
@Override
public String toString() {
return "Person{"
+ "fullName='" + fullName + '\''
+ ", age=" + age
+ ", retired=" + retired
+ '}';
}
} To see the result, pass the object to System.out.println. The toString() method is called automatically:
public class PersonExample {
public static void main(String[] args) {
Person person = new Person("John Smith", 56, false);
System.out.println(person);
}
} Program output:
Person{fullName='John Smith', age=56, retired=false} Rules for a good toString() implementation:
- include the class name and the key fields, so the string helps with debugging and reading logs;
- you don't have to include every field: it's usually better to leave out passwords, tokens, large collections and back-references to a parent object;
- the method must not change the object's state or run heavy computations;
- always add
@Override, so the compiler catches a typo in the method name.
You can also call the method explicitly with System.out.println(person.toString()), but inside println() that's redundant. It's also riskier: if person is null, the explicit call throws a NullPointerException, while println(person) just prints null.
When toString() is called implicitly
You rarely call toString() yourself. Usually the language or a library calls it for you:
System.out.println(obj)andSystem.out.print(obj);- string concatenation:
"Customer: " + person; String.valueOf(obj),String.format("%s", obj),StringBuilder.append(obj);- loggers (SLF4J, Log4j) when an object is inserted into a log message;
- the IDE debugger when it shows a variable's value.
These ways of getting a string don't all handle null the same way:
| Expression | Object is not null | Object is null |
|---|---|---|
obj.toString() | result of toString() | NullPointerException |
String.valueOf(obj) | result of toString() | the string "null" |
"" + obj | result of toString() | the string "null" |
Objects.toString(obj, "no data") | result of toString() | the fallback value |
Autogenerating toString(): IDE, Lombok, record
You don't have to write toString() by hand for every class:
- IntelliJ IDEA:
Alt+Insert(Cmd+Non macOS) → toString(). The default template produces the same format as the example above:Person{fullName='...', age=56, retired=false}. - Eclipse: Source → Generate toString()...
- Lombok: put the
@ToStringannotation on the class (or@Data, which includes it). You can leave out individual fields with@ToString.Exclude.
Records (record, a standard feature since Java 16) get toString() automatically, together with equals() and hashCode():
public record Point(int x, int y) { }
System.out.println(new Point(1, 2)); // Point[x=1, y=2] A record's toString() uses square brackets, while the IntelliJ IDEA template uses curly braces.
toString() for collections and arrays
Collections already override toString(): lists and sets print their elements in square brackets ([a, b, c]), and maps print them in curly braces ({key=value}). Each element is still printed with its own toString(). If the element's class doesn't override it, you'll get a list like [oop.Person@5e2a3193, oop.Person@6d06d69c].
Arrays are different: they don't override toString(), so System.out.println(numbers) prints something like [I@1b6d3586. To print the elements of an array, use Arrays.toString(), covered in detail in a separate lesson: Arrays.toString() Method in Java.
How toString() relates to equals() and hashCode()
toString(), equals() and hashCode() are the three Object methods that are usually overridden together. The difference is that equals() and hashCode() follow a strict contract (equal objects must have equal hash codes), while overriding toString() is only a recommendation.
There's also a less obvious link: the default toString() implementation uses hashCode(). If a class overrides hashCode() but not toString(), two equal objects print the same string, for example oop.Person@1f and oop.Person@1f.
Where developers get tripped up with toString()
- Infinite recursion. Suppose
Departmentprints its list ofEmployeeobjects and eachEmployeeprints itsDepartment. Then calling toString() ends in aStackOverflowError. On one side of the relationship, print only an ID or a name. - Leaking sensitive data. toString() output often ends up in logs, so never include passwords, card numbers or tokens.
- Typo in the method name. A method named
tostring()ortoString(String s)is a new method, not an override. Without@Overridethe compiler won't complain, and println() keeps printingPerson@.... - Parsing the toString() result in application logic. The string format is meant for people and can change at any time. Use equals() for comparisons, and a JSON library or a dedicated method for serialization.
Frequently Asked Questions
Is the number after @ in Person@5e2a3193 the object's memory address?
No. It's the result of hashCode() in hexadecimal. In modern JVMs (HotSpot), the default hash code is generated by the JVM and isn't taken from the object's address. The garbage collector can move the object, but its hash code stays the same.
Do I have to override toString()?
No, the program works fine without it. Unlike equals() and hashCode(), toString() has no contract that can break collections. Still, the Javadoc recommends overriding toString() in every class, because it makes debugging and reading logs much easier.
What does toString() return for an enum?
By default it returns the constant's name, the same value as name(). You can override toString() in an enum, for example to return a human-readable label. name() is declared final, so it always returns the constant's name exactly as written in the code.
What happens if an overridden toString() returns null?
The code compiles, and println() or string concatenation prints null. It's still bad practice: calling code expects a non-null string, and obj.toString().length() will throw a NullPointerException. Always return at least the class name.
Comments