It's a compiler thing. If the right operand for concatenation is an object, the object is sent the toString() method whereas if the operand is a primitive then the compiler knows which type-specific behavior to use to convert the primitive to an String.