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 toStringString 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:

[sourcecode language=”java”]
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)
[/sourcecode]

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:

[sourcecode language=”java”]
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)
[/sourcecode]

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.

[sourcecode language=”java”]
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)
[/sourcecode]

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):

[sourcecode language=”java”]
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)
[/sourcecode]

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:

[sourcecode language=”java”]
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)
[/sourcecode]

[sourcecode language=”java”]
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)
[/sourcecode]

[sourcecode language=”java”]
public String toString() {
return ReflectionToStringBuilder.toString(this);
}

// Average performance with JMH (ops/s)
// (min, avg, max) = (14172,485, 23204,479, 30754,901)
[/sourcecode]

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.

21 thoughts on “Who Cares About toString Performance?

    1. Yes, that’s why “concise” is very important. You should avoid looping on a toString method.

    2. whats suprising for me is i tried also using StringBuffer and result is better than StringBuilder , its 0.256 sec for buffer and 0.67 sec for builder and 12 sec for string concat with +

      1. and how is this possible, ideally StringBuffer Should perform well right? could someone please explain this?

    3. Are your benchmark open source on github? I would like to compare them with mine.

  1. Another nice trick is to use Immutable objects when feasible.

    This allows for generating toString description once and for all at mutation (creation of the immutable) time. From there on, toString will just read a string, and performance is therefore unbeatable.

    The only downside being the lack of direct support of Immutable objects in most Java frameworks.

  2. it’s strange that String concat with + takes nearly the same time as String builder…there may be some complier optimize

  3. good tutorial on toString()….above stuff is always a roadmap for young developers like me.for example, thanks for your nice tips, hope you continue like this on many more within core JAVA

  4. 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).

    best

  5. good tutorial,but i don’t understand, in your table with the average performance the best is “ReflectionToStringBuilder”, why not use it?

  6. The table is:
    1 – from most performant to less performant
    or
    2 – from less performant to most performant

    1. I’m interested in the performance of String.format() as well. Could you please cover it in your test?

  7. Nice post !! We, as developer, should care performance not convenient way to use toString.

  8. Today the JVM compiles the + symbol into a string builder (in most cases). I am afraid to say that this line is incorrect.
    The JVM does not compile the java code, It’s JAVA COMPILER who does this.

Leave a Reply