Thursday 19 January 2012

That darned "No suitable ClassLoader found for grab" with groovyc and java -cp


Update Apr 7, 2012: Hmm. Seems this is a very popular post. I intend to expand it and cover how to approach the details more clearly.

I like groovy.
Problem is, we don't have it installed on our live servers.
So I have to compile the groovy code into java class files and deploy them.
But when you're using the @Grab annotation, things go bad.

I kinda like using @Grab to snarf the specific dependencies, but you always run into the dreaded:

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: No suitable ClassLoader found for grab

Now all that @Grab does is download the jars you needed and pops them into ~/.groovy/grapes.
Once it's done it's job you don't need it for either running groovy scripts or for the java class.
Here's a rather artificial contrived explanation of how to get around the ClassLoader issue.

(Of course you could just use grape directly, or specify your own .m2 jars or whatever, but I noted this question open in a few places, so I contrived this example)

First create your script, say, x.groovy...

@Grab(group='com.gmongo', module='gmongo', version='0.8')
import com.gmongo.GMongo
// Instantiate a com.gmongo.GMongo object instead of com.mongodb.Mongo
def mongo = new GMongo("the.mongo.ip.address", 27017)
def db = mongo.getDB("some_database")
println db.my_collection.find([SomeKey:[$ne:"0"],SomeOtherKey:"0"]).count()

Now run it:

groovy x.groovy
149845

Cool.
Now we use groovyc to compile it...

groovyc -d classes x.groovy

But when we try to run it using java...

# Building CLASSPATH here purely for clarity...
# We compiled the groovy class into classes:
CLASSPATH="classes"
# We need the gmongo jar:
CLASSPATH="$CLASSPATH:~/.groovy/grapes/com.gmongo/gmongo/jars/gmongo-0.8.jar"
# And because tis is just an example, I knew I needed this one as well:
CLASSPATH="$CLASSPATH:~/.groovy/grapes/org.mongodb/mongo-java-driver/jars/mongo-java-driver-2.5.2.jar"
# And I dev on a Mac and use MacPorts, so groovy is installed in /opt/local/share:
CLASSPATH="$CLASSPATH:/opt/local/share/java/groovy/lib/*"
# Now run it:
java -cp $CLASSPATH x
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: No suitable ClassLoader found for grab
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 ...elided for brevity...
 at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:165)
 at x.<clinit>(x.groovy)
</clinit>

Rats. So we need to remove the @Grab now that it's done it's work and downloaded the jars...

//@Grab(group='com.gmongo', module='gmongo', version='0.8')
import com.gmongo.GMongo
// Instantiate a com.gmongo.GMongo object instead of com.mongodb.Mongo
def mongo = new GMongo("192.168.4.112", 27017)
def db = mongo.getDB("some_database")
println db.my_collection.find([SomeKey:[$ne:"0"],SomeOtherKey:"0"]).count()

And re-compile using the specific gmongo jar:

groovyc -cp ~/.groovy/grapes/com.gmongo/gmongo/jars/gmongo-0.8.jar -d classes x.groovy

And now we can run it as before

# Building CLASSPATH here purely for clarity...
...elided...
java -cp $CLASSPATH x
149845

Hope that explains it for those stuck with that issue.

Oh, and if you need to see the classpath from within a script go here: http://blog.blindgaenger.net/print_groovys_classpath_for_debugging.html
Waaaay cool.

2 comments:

  1. Thanks for posting this explanation. It's summer 2013 now and no easier solution in sight ?

    ReplyDelete
  2. Dear lord it's like 2018 and i still have to deal with stuff like this!
    Thanks for the workaround Kim!

    ReplyDelete