How to create toString() method in Java?

In this post, I am sharing several ways of creating toString method in Java. The toString() method returns a string representation of the object.

Before we move on, there are a few questions that you should answer first. The main question would be whether a custom toString() implementation is really needed or we can use the default Java Object.toString() one. Another one would be about the best format of the string representation of the object.

There is no straightforward yes/no answers to both questions. It all depends on your requirements or needs. Object string representations will be used mostly by logging systems and will be used to see important information in a “human-readable” format. Hence toString() method should be overridden by any class that you will need to present its internal state.

In terms of the best format of the string representation, Java documentation recommends that an object’s string representation should be a concise but informative representation that is easy for a person to read. So that string representation should contain all important details that need to be presented, however, it should not have any other details that are never used.

Please note that sometimes you may need string representations in different formats for the same object. In these cases, you may implement several toString() like method e.g. toAuditString(), toDetailedString() etc. However, presented solutions will also apply to these extensions.

So let’s crack on.

We will use the following Java class representing a user with a few fields.

public class User {
    private String id;
    private String name;
    private String email;
    private String address;
    private int age;

    // ... setters and getters
}

We will implement toString() method using several different techniques:

String concatenation

String concatenation is the most straightforward and flexible technique. It uses the Java mechanism of generating a string. This approach, however, it tricky to get the format right. Also, any changes to the format (e.g. from field="value" to field:value) would require a manual update.

public class User {
    
    // ... fields, setters and getters

    @Override
    public String toString() {
        return "User(" +
                "id='" + id + '\'' + 
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", address='" + address + '\'' +
                ", age=" + age +
                ')';
    }
}

StringBuilder class

This approach uses a mutable sequence of characters to generate a string representation of an object. StringBuilder was introduced in Java 1.5.

Please note that instances of StringBuilder are not safe for use by multiple threads. If you need a thread-safe solution then you should use StringBuffer, which uses synchronization under the hood.

public class User {
    
    // ... fields, setters and getters

    @Override
    public String toString() {
        return new StringBuilder("User(")
                .append("id='").append(id).append('\'')
                .append(", name='").append(name).append('\'')
                .append(", email='").append(email).append('\'')
                .append(", address='").append(address).append('\'')
                .append(", age=").append(age)
                .append(')').toString();
    }
}

This approach is good to add certain parts conditionally to the string representation of the object. Let’s use an approach where null values and age equal to 0 (zero) won’t be included in the returned string.

public class User {
    
    // ... fields, setters and getters

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("User(");
        if (id != null) sb.append("id='").append(id).append('\'');
        if (name != null) sb.append(", name='").append(name).append('\'');
        if (email != null) sb.append(", email='").append(email).append('\'');
        if (address != null) sb.append(", address='").append(address).append('\'');
        if (age > 0) sb.append(", age=").append(age);
        sb.append(')');
        return sb.toString();
    }
}

Example of string outputs for above approaches:

User(id='U000187', name='John Smith', email='john.smith@foobar.com', address='null', age=0)
User(id='U000187', name='John Smith', email='john.smith@foobar.com')

StringJoiner class

StringJoiner class got introduced in Java 1.8. Its main goal is to construct from multiple strings joined by a defined delimiter. It also allows optional prefix and suffix.

public class User {
    
    // ... fields, setters and getters

    @Override
    public String toString() {
        return new StringJoiner(", ", "User(", ")")
                .add("id='" + id + "'")
                .add("name='" + name + "'")
                .add("email='" + email + "'")
                .add("address='" + address + "'")
                .add("age=" + age)
                .toString();
    }
}

Similarly like in the StringBuilder approach, StringJoiner can use conditional generation of string representation parts. In addition to that you can use StringJoiner.setEmptyValue(CharSequence) method with a value to be used if there is no StringJoiner.add() methods called.

public class User {
    
    // ... fields, setters and getters

    @Override
    public String toString() {
        final StringJoiner joiner = new StringJoiner(", ", "User(", ")");
        joiner.setEmptyValue("User(empty)");
        if (id != null) joiner.add("id='" + id + "'");
        if (name != null) joiner.add("name='" + name + "'");
        if (email != null) joiner.add("email='" + email + "'");
        if (address != null) joiner.add("address='" + address + "'");
        if (age > 0) joiner.add("age=" + age);
        return joiner.toString();
    }
}

Example of string outputs:

User(id='U000187', name='Mr. String Joiner') # with some missing fields
User(empty)                                  # with no fields and age

Apache Commons Lang

This approach uses Apache Commons Lang library. With this approach you do not have to worry about fields concatenation and format as ToStringBuilder will do it for you.

public class User {
    
    // ... fields, setters and getters

    @Override
    public String toString() {
        return new ToStringBuilder(this)
                .append("id", id)
                .append("name", name)
                .append("email", email)
                .append("address", address)
                .append("age", age)
                .toString();
    }
}

This library has a very nice feature - ToStringBuilder constructor also accepts ToStringStyle parameter, which impacts the style of the output string.

A few examples:

# default style
User@78e94dcf[id=U001,name=Mr. ToStringBuilder,email=user@apache.commons.com,address=<null>,age=35]

# ToStringBuilder(this, ToStringStyle.JSON_STYLE)
{"id":"U001","name":"Mr. ToStringBuilder","email":"user@apache.commons.com","address":null,"age":35}

# ToStringBuilder(this, ToStringStyle.SIMPLE_STYLE)
U001,Mr. ToStringBuilder,user@apache.commons.com,<null>,35

# ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
User[id=U001,name=Mr. ToStringBuilder,email=user@apache.commons.com,address=<null>,age=35]

# ToStringBuilder(this, ToStringStyle.NO_FIELD_NAMES_STYLE)
User@78e94dcf[U001,Mr. ToStringBuilder,user@apache.commons.com,<null>,35]

Google Guava

This approach uses the Google Guava library. Similarly to Apache Commons Lang you do not have to worry about fields concatenation and format as MoreObjects.ToStringHelper will do it for you. The only drawback of this solution is that a format of a string representation of an object is predefined and cannot be amended via configuration.

public class User {

    // ... fields, setters and getters

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("id", id)
                .add("name", name)
                .add("email", email)
                .add("address", address)
                .add("age", age)
                .toString();
    }
}

Happy coding!