A blog mainly about Java

Who Cares About toString Performance?

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.

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:

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.

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:

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

And again, all this is important if you invoke the toString method often. If not, performance is not really an issue.

Well, the benchmark here is short on the explanation side: why is it so?

indeed, maybe it’s due to the current code using StringBuilder without defining its initial size, which is a good practice.

Here the compiler could go the extra length to set something which is bigger for sure than the minimal size of the content put in. Indeed, there are numerous plain strings in the example, so the compiler could add them up, then multiple the resulting size by two (for example).

In turn this would avoid the creation of some intermediate arrays (in StringBuilder) and thus be more performant.

Anyway, it’s a wild guess, it would be way better to go the extra length to understand why (provided one knows enough, and I don’t know how to read the byte code to figure this out).