This behavior is correct, because java.lang.Integer
overrides Object.toString()
with its own implementation. If your assumption was correct then it would mean that you can break overridden method by forcing to use an implementation from parent class.
Consider following Groovy script:
Object.metaClass.toString = {
System.out.println("the string is $delegate")
}
class GroovyClassWithNoToString {}
class GroovyClassWithToString {
@Override
String toString() {
return "aaaa"
}
}
new GroovyClassWithNoToString().toString()
new GroovyClassWithToString().toString()
1500.toString()
Runtime.runtime.toString()
When you run it you will see something like:
the string is GroovyClassWithNoToString@3a93b025
the string is java.lang.Runtime@128d2484
You can see that GroovyClassWithNoToString.toString()
called Object.toString()
method and its modified version, also Runtime.toString()
calls Object.toString()
- I picked this class as an example of pure Java class that does not override toString()
method.
As you can see overriding toString()
method from Object
level makes sense for classes that base on Object.toString()
implementation. Classes that provide their own implementation of toString()
wont use your dynamically modified method. It also explains why following code works:
Object.metaClass.printMessage = {
System.out.println("Hello!")
}
1500.printMessage()
In this example we are adding a new method called printMessage()
to Object
class and all classes that don't override this method will use this dynamic method we just created. Integer
class does not have method like that one so it's gonna print out:
Hello!
as expected.
Also keep in mind that toString()
should return a String
and it's better to not print anything to output inside this method - you can end up with nasty StackOverflowError
caused by circular calls to toString()
method.
UPDATE: How toString()
method is being picked by Groovy runtime?
Let me show you under the hood what happens when we call following script:
Object.metaClass.toString = {
System.out.println("Hello!")
}
1500.toString()
and let's see what does Groovy during the runtime. Groovy uses Meta Object Protocol (MOP) to e.g. invoke any method called in a Groovy code. In short, when you call any Java or Groovy method it uses MOP as an intermediate layer to find an execution plan for a method - call it directly or use e.g. a method that was injected dynamically.
In our case we use plain Java class - Integer
. In this case Groovy will create an instance of PojoMetaMethodSite
class to meta class implementation for Java class - an Integer
. Every meta method is executed using one of the Groovy groovy.lang.MetaClass
implementation. In this case groovy.lang.MetaClassImpl
is being used. One of the last methods that picks a method to execute is MetaClassImpl.getMethodWithCachingInternal(Class sender, CallSite site, Class [] params)
. If you put a breakpoint in the beginning of this method and run a script with a debugger, you will see that this method is executed with following parameters:

In line 1331 you can see that helper method called chooseMethod(e.name, methods, params)
is being used:
cacheEntry = new MetaMethodIndex.CacheEntry (params, (MetaMethod) chooseMethod(e.name, methods, params));
This method is responsible for picking the right method to execute when we try to invoke toString()
on Integer
object. Let's get there and see what happens. Here is what this method implementation looks like:
/**
* Chooses the correct method to use from a list of methods which match by
* name.
*
* @param methodOrList the possible methods to choose from
* @param arguments
*/
protected Object chooseMethod(String methodName, Object methodOrList, Class[] arguments) {
Object method = chooseMethodInternal(methodName, methodOrList, arguments);
if (method instanceof GeneratedMetaMethod.Proxy)
return ((GeneratedMetaMethod.Proxy)method).proxy ();
return method;
}
Source: https://github.com/apache/groovy/blob/GROOVY_2_4_X/src/main/groovy/lang/MetaClassImpl.java#L3158
Now let's see what parameters are received when we call our script:

What is most interesting in our case is the first element of methodOrList.data
. It's a method object of:
public java.lang.String java.lang.Integer.toString()
which is the method toString()
that Integer
class overrides from its parent class. Groovy runtime picks this method, because it is the most accurate from the runtimes point of view - it is the most specific method for Integer
class provided. If there is no toString()
method overridden at the class level (e.g. Runtime
class example I mentioned earlier) then the best candidate for invoking toString()
method is a ClosureMetaMethod
provided by us in Object.metaClass.toString = ...
. I hope it gives you a better understanding of what happens under the hood.