Java toString() Method

In the last lesson you learned about the Java Object class. One free method it hands you is toString(). The trouble: print your object and you get junk like Student@1b6d3586 instead of the name and grade.

The fix is to override toString() so your object prints a clear, human description of itself. Let’s learn how.

πŸ€” What the default toString() gives you

  • Every object already has a toString(), inherited from Object.
  • When you print an object, Java calls it for you.
  • The trouble is the default version is almost useless.
class Student {
String name;
int grade;
Student(String name, int grade) {
this.name = name;
this.grade = grade;
}
}
public class Main {
public static void main(String[] args) {
Student s = new Student("Alex", 90);
System.out.println(s); // prints the default toString()
}
}

Output

Student@1b6d3586

That is the default toString() in action.

  • It returns the class name, an at sign, then a hex code.
  • The code is the object’s hash code in hex. It is not the memory address.
  • It shows no name and no grade. It tells you nothing about the object.

So we need to replace it.

πŸ› οΈ Overriding toString()

To fix this you write your own toString() that returns a String describing the object. Put @Override on top so the compiler checks the signature:

class Student {
String name;
int grade;
Student(String name, int grade) {
this.name = name;
this.grade = grade;
}
@Override
public String toString() {
return "Student{name=" + name + ", grade=" + grade + "}";
}
}

Now the same System.out.println(s) prints the real data.

public class Main {
public static void main(String[] args) {
Student s = new Student("Alex", 90);
System.out.println(s); // now calls YOUR toString()
}
}

Output

Student{name=Alex, grade=90}
  • The method must be public String toString() with no parameters, matching Object.
  • @Override forces the compiler to confirm this.
  • Typo the name or add a parameter, and the build fails instead of silently doing nothing.

πŸͺ„ Java calls it automatically

You never typed s.toString() above, yet your version ran. Java calls it for you in two common spots:

  • When you print an object. System.out.println(s) calls s.toString() first.
  • When you join an object to a String with +.

Watch both at once.

public class Main {
public static void main(String[] args) {
Student s = new Student("Alex", 90);
System.out.println(s); // printing calls toString()
String line = "Top: " + s; // joining with + calls toString()
System.out.println(line);
}
}

Output

Student{name=Alex, grade=90}
Top: Student{name=Alex, grade=90}

Java needed a String, so it called toString() on the student and pasted the result in.

  • Write the method once, and the object reads cleanly everywhere it turns into text.
  • You get it in prints, joins, and logs.
  • Experienced developers add it on almost every new class.

🧱 Building a clear toString()

A good toString() includes the key fields with labels, in the style ClassName{field1=value1, field2=value2}:

class Account {
String owner;
String id;
double balance;
Account(String owner, String id, double balance) {
this.owner = owner;
this.id = id;
this.balance = balance;
}
@Override
public String toString() {
return "Account{owner=" + owner
+ ", id=" + id
+ ", balance=" + balance + "}";
}
}
public class Main {
public static void main(String[] args) {
Account a = new Account("Maria", "AC-100", 250.0);
System.out.println(a);
}
}

Output

Account{owner=Maria, id=AC-100, balance=250.0}
  • The labels owner= and balance= tell a reader which value is which.
  • Without them you just see two numbers and a name. You have to guess.
  • Include fields that tell objects apart, like a name or an id.
  • Skip huge or noisy fields, or replace them with a short summary like a list’s size.

βš™οΈ IDEs and records can write it for you

Typing toString() by hand gets boring, and you rarely have to.

  • Every major editor generates it. Pick β€œtoString()” from a menu, then choose the fields.
  • The editor writes a correct method. You read it, tweak it, move on.
  • A record builds toString() automatically, no code needed.
record Point(int x, int y) { }
public class Main {
public static void main(String[] args) {
Point p = new Point(3, 5);
System.out.println(p); // record made toString() for us
}
}

Output

Point[x=3, y=5]

We never wrote a toString(), yet the record printed a clean line.

  • For plain data holders, a record gives you a readable toString() for free.
  • For regular classes, let the editor generate it.

πŸ› toString() for debugging and logging

A readable toString() lets you see object state the instant something goes wrong. Printing a whole list is the classic case, because a list calls toString() on each item.

import java.util.List;
public class Main {
public static void main(String[] args) {
List<Student> roster = List.of(
new Student("Alex", 90),
new Student("Maria", 85)
);
System.out.println(roster); // each Student uses its toString()
}
}

Output

[Student{name=Alex, grade=90}, Student{name=Maria, grade=85}]
  • The list printed every student cleanly because each had a good toString().
  • Without the override you would get a line full of Student@... codes.
  • Logging works the same way. logger.info("Saving " + student) calls toString() through the +.
  • That turns a mystery bug into a quick read.

⚠️ Common Mistakes

A few toString() slip-ups come up again and again.

  • Not overriding it at all. You print the object and get a useless ClassName@hash. Always add a toString() to classes you plan to print or log.
class Student { // ❌ no toString(), prints Student@1b6d3586
String name;
int grade;
}
class Student {
String name;
int grade;
@Override
public String toString() { // βœ… readable output
return "Student{name=" + name + ", grade=" + grade + "}";
}
}
  • Forgetting @Override. Without it, a typo in the name or a stray parameter creates a brand new method, and the useless default still runs with no warning.
public String tostring() { // ❌ wrong name, silently NOT an override
return "Student{name=" + name + "}";
}
@Override
public String toString() { // βœ… compiler verifies the signature
return "Student{name=" + name + "}";
}
  • Exposing sensitive fields. Never dump passwords, tokens, or card numbers into toString(). They end up in logs and on screens where they do not belong.
@Override
public String toString() {
return "User{name=" + name + ", password=" + password + "}"; // ❌ leaks the secret
}
@Override
public String toString() {
return "User{name=" + name + "}"; // βœ… leave secrets out
}

βœ… Best Practices

A few habits keep your toString() useful and safe.

  • Always write @Override. It catches a wrong name or signature at compile time, the moment you build.
  • Include the key fields with labels. Use a field=value style so a reader knows what each value means without guessing.
  • Leave out secrets and noise. Skip passwords and tokens. Summarize big fields, like printing a list’s size instead of its full contents.
  • Let the editor or a record generate it. Hand-typing is error-prone for many fields. Generated code is correct and fast.
  • Keep it cheap and side-effect free. toString() runs a lot during debugging and logging, so it should not do heavy work or change anything.
  • Make it readable, not parseable. toString() is for humans reading logs. If you need machine-readable data, use a proper format instead.

🧩 What You’ve Learned

Nicely done. Let’s recap the toString() method.

  • βœ… The default toString() from Object returns ClassName@hexhashcode, which tells you nothing useful.
  • βœ… You override toString() as public String toString() with @Override to return a readable description.
  • βœ… Java calls it automatically when you print an object or join it to a String with +.
  • βœ… A good toString() includes the key fields with labels, in a style like ClassName{field=value}.
  • βœ… IDEs can generate it, and records create it for you automatically.
  • βœ… A clear toString() makes debugging and logging fast, including printing whole lists of objects.
  • βœ… Never put sensitive fields like passwords or tokens into toString().

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What does the default toString() inherited from Object return?

    Why: By default toString() returns the class name, an at sign, and the object's hash code in hex, which is not useful.

  2. 2

    When does Java call toString() automatically?

    Why: Printing an object and concatenating it with a String both call toString() for you.

  3. 3

    Why should you put @Override on your toString()?

    Why: @Override makes the compiler confirm you correctly match Object's toString(), turning typos into compile errors.

  4. 4

    Which field should you NOT include in toString()?

    Why: Secrets like passwords and tokens must stay out of toString() so they do not leak into logs or screens.

πŸš€ What’s Next?

You can now make any object print a clear description of itself. Next you will learn the other famous Object method, the one that decides when two objects count as the same. It is just as common to override, and it has a few rules you need to know.

Java equals() Method

Share & Connect