Who cares about toString performance? Nobody! Except when you have huge amount of data being processed in a batch that does plenty of logging using toString . Then, you investigate why it’s slow, realize that the toString method is mostly implemented using introspection and can be optimized.

But first, let’s have a look at the Javadoc to remember what a Object.toString should do: “returns a textually representation of an object and it should be a concise but informative representation that is easy for a person to read. It is recommended that all subclasses override this method“. What’s interesting here are the words “concise” and “informative“. Our beloved IDEs tend to generate equals / hashcode / toString methods for us… and we usually leave them like that. Moreover, our beloved IDEs give us several choices to generate our toString : String concatenation (using the + symbol), StringBuffer , StringBuilder , ToStringBuilder (Commons Lang 3), ReflectionToStringBuilder (Commons Lang 3), Guava or Objects.toString... Which one to choose?

If you want to know which toString implementation is more efficient, you don’t guees, you mesure! And you need to use JMH. I’ve already blogged about it, so I won’t go into too much details on how it works.

For this benchmark I’ve created a complex graph of objects (using inheritance, collections and so on), and I’ve used all the different toString implementations generated by my IDE to see which one is more performant. One rule of thumb already: be concise. No matter which technic you use (see below), generating a toString for a few attributes, or, all attributes (including inheritance, dependencies and collections), has a huge performance impact.

String concatenation with the + symbol

Let’s start with the most performant method: string concatenation with + symbol. What used to be considered evil yesterday (“do not concatenate Strings with + !!!“), has become cool and efficient! Today the JVM compiles the + symbol into a string builder (in most cases). So, do not hesitate, use it. The only downside is that null values are not handled, you need to do it yourself.

Check the average performance with JMH in the comments below:

public String toString() { return "MyObject{" + "att1='" + att1 + '\'' + ", att2='" + att2 + '\'' + ", att3='" + att3 + '\'' + "} " + super.toString(); } // Average performance with JMH (ops/s) // (min, avg, max) = (140772,314, 142075,167, 143844,717)

String concatenation with Objects.toString

Java SE 7 brought the Objects class and with it a few static methods. The advantage of Objects.toString is that it deals with null values, and can even set a default value if null . The performance is slightly lower than the previous code, but nulls are handled:

public String toString() { return "MyObject{" + "att1='" + Objects.toString(att1) + '\'' + ", att2='" + Objects.toString(att2) + '\'' + ", att3='" + Objects.toString(att3) + '\'' + "} " + super.toString(); } // Average performance with JMH (ops/s) // (min, avg, max) = (138790,233, 140791,365, 142031,847)

StringBuilder

Another technic is to use StringBuilder . Here it’s really difficult to tell which technic performs better. As I said, I’ve used complex object graphs ( att1 , att2 and att3 are just here for readability) and JMH gives more or less the same results. These last 3 technics are quite similar in terms of performance.

public String toString() { final StringBuilder sb = new StringBuilder("MyObject{"); sb.append("att1='").append(att1).append('\''); sb.append(", att2='").append(att2).append('\''); sb.append(", att3='").append(att3).append('\''); sb.append(super.toString()); return sb.toString(); } // Average performance with JMH (ops/s) // (min, avg, max) = (96073,645, 141463,438, 146205,910)

Guava

Guava has few helper classes: one of them helping you to generate toString . It is less performant than pure JDK APIs, but it can give you a few extra services (I’m talking about Guava here):

public String toString() { return Objects.toStringHelper(this) .add("att1", att1) .add("att2", att2) .add("att3", att3) .add("super", super.toString()).toString(); } // Average performance with JMH (ops/s) // (min, avg, max) = (97049,043, 110111,808, 114878,137)

Commons Lang3

Commons Lang3 has a few technics to generate toString : from a builder to an introspector. As you can guess, introspection is easier to use, has less lines of code, but has a terrible performance impact:

public String toString() { return new ToStringBuilder(this) .append("att1", att1) .append("att2", att2) .append("att3", att3) .append("super", super.toString()).toString(); } // Average performance with JMH (ops/s) // (min, avg, max) = ( 73510,509, 75165,552, 76406,370)

public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); } // Average performance with JMH (ops/s) // (min, avg, max) = (31803,224, 34930,630, 35581,488)

public String toString() { return ReflectionToStringBuilder.toString(this); } // Average performance with JMH (ops/s) // (min, avg, max) = (14172,485, 23204,479, 30754,901)

Conclusion

Today with the JVM optimisation, we can safely use the + symbol to concatenate Strings (and use Objects.toString to handle nulls). With the utility class Objects that is built-in the JDK, no need to have external frameworks to deal with null values. So, out of the box, the JDK has better performance than any other technic described in this article (if you have another framework/technic, please leave a comment and I’ll give it a try).

As a sum up, here is a table with the average performance from JMH (from most performant to less performant):

Technic Average ops/s String concat with + 142.075,167 String builder 141.463,438 Objects.toString 140.791,365 Guava 110.111,808 ToStringBuilder (append) 75.165,552 ToStringBuilder (reflectionToString) 34.930,630 ReflectionToStringBuilder 23.204,479