Tuesday, August 31, 2010

Using groovyc To Compile Groovy Scripts

For most Groovy scripts I use, I simply run the script from its Groovy source code as-is and allow the compilation to take place implicitly.  However, it can be helpful at times to use groovyc to compile Groovy code into .class files and then execute those .class files via the normal Java launcher (java).  The groovyc compiler is also a necessity when mixing Groovy and Java code in the same compilation step.

For traditional Java, such as the HelloWorld.java class shown next, javac is used to compile the class into a .class file and then java is used to run that class file.  The example class is shown first followed by a screen snapshot showing the compilation and execution process.

HelloWorld.java
import static java.lang.System.out;
import java.util.Date;

public class HelloWorld
{
   public static void main(String[] args)
   {
      out.println("Hello, " + args[0] + ", the time is " + new Date());
   }
}


When running Groovy scripts, I usually run the script directly.  A simple Groovy script equivalent of the above Java class is shown next and is followed by a screen snapshot demonstrating running the script directly.  There is no .class file in the directory.

helloToWorld.groovy
println "Hello, ${args[0]}, the time is ${new Date()}"


The above approach to running Groovy scripts is the approach I typically take.  However, there are times when it is advantageous to explicitly compile a Groovy script into a .class file using groovyc and then run it just as one would run a file created with javac.  This is demonstrated in the next screen snapshot for the simple helloToWorld.groovy script shown above.


To run the Groovy script with the normal java launcher, it was necessary to include the Groovy JAR file on the classpath (I took advantage of Java 6's JAR wildcard specification feature in this case to include all JARs in the Groovy installation lib directory).  I also needed to include the classpath entries within quotes to make it work properly.

Had I not included the Groovy JAR in the classpath for the java launcher, I would see an error like this:

java.lang.NoClassDefFoundError: groovy/lang/Script
 at java.lang.ClassLoader.defineClass1(Native Method)
 at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
 at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
 at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
 at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
 at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
 at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
Caused by: java.lang.ClassNotFoundException: groovy.lang.Script
 at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
 ... 12 more
Could not find the main class: helloToWorld.  Program will exit.
Exception in thread "main" 

Had I not included the classpath in quotes, the error looks like this:

java.lang.NoClassDefFoundError: Files\Groovy\Groovy-1/7/4\lib\*
Caused by: java.lang.ClassNotFoundException: Files\Groovy\Groovy-1.7.4\lib\*
 at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
Could not find the main class: Files\Groovy\Groovy-1.7.4\lib\*.  Program will exit.
Exception in thread "main"

In the examples above, the Groovy source (one line) was much shorter than the Java source code. Notice that the respectively compiled .class files, however, are much different in their relative size.  As the last screen snapshot demonstrated, the helloToWorld.class file generated with groovyc is 7524 bytes compared to the 703 bytes of the HelloWorld.class generated with javac.  The javap command is useful here to explain the difference in compiled sizes.  The output from running javap against the respective disassembled .class files is shown next.

javap-Disassembled javac-Generated HelloWorld.class File
Compiled from "HelloWorld.java"
public class HelloWorld extends java.lang.Object{
    public HelloWorld();
    public static void main(java.lang.String[]);
}

javap-Disassembled groovyc-Generated helloToWorld.class File
Compiled from "helloToWorld.groovy"
public class helloToWorld extends groovy.lang.Script{
    public static java.lang.Long __timeStamp;
    public static java.lang.Long __timeStamp__239_neverHappen1283320660787;
    public helloToWorld();
    public helloToWorld(groovy.lang.Binding);
    public static void main(java.lang.String[]);
    public java.lang.Object run();
    public java.lang.Object this$dist$invoke$4(java.lang.String, java.lang.Object);
    public void this$dist$set$4(java.lang.String, java.lang.Object);
    public java.lang.Object this$dist$get$4(java.lang.String);
    protected groovy.lang.MetaClass $getStaticMetaClass();
    static {};
    public java.lang.Object super$3$getProperty(java.lang.String);
    public java.lang.String super$1$toString();
    public void super$3$setProperty(java.lang.String, java.lang.Object);
    public void super$1$notify();
    public void super$3$println();
    public void super$1$notifyAll();
    public void super$3$print(java.lang.Object);
    public void super$3$printf(java.lang.String, java.lang.Object[]);
    public java.lang.Object super$1$clone();
    public java.lang.Object super$3$evaluate(java.lang.String);
    public void super$1$wait();
    public groovy.lang.MetaClass super$2$getMetaClass();
    public void super$1$wait(long, int);
    public void super$2$setMetaClass(groovy.lang.MetaClass);
    public java.lang.Class super$1$getClass();
    public groovy.lang.Binding super$3$getBinding();
    public void super$1$finalize();
    public void super$3$printf(java.lang.String, java.lang.Object);
    public void super$3$setBinding(groovy.lang.Binding);
    public void super$1$wait(long);
    public void super$3$run(java.io.File, java.lang.String[]);
    public java.lang.Object super$3$evaluate(java.io.File);
    public void super$3$println(java.lang.Object);
    public boolean super$1$equals(java.lang.Object);
    public java.lang.Object super$3$invokeMethod(java.lang.String, java.lang.Object);
    public int super$1$hashCode();
    static java.lang.Class class$(java.lang.String);
}

As the output from the javap class disassembler demonstrates, the shorter Groovy code translates to a much larger .class file because of all the support that must go into it to provide that Groovy magic.

The groovyc compiler is a necessity for compiling Groovy and Java together simultaneously.  It can also be useful for compiling .class files to execute without need for recompilation every time the script is run.  Finally, groovyc's output provides insight into the magic behind Groovy for those who are interested.  If nothing else, using groovyc explicitly also helps one realize how much is done automatically by the groovy native launcher.

2 comments:

Joseph Lucca said...

Thank you. Your post is as relevant in 2015 as it was in 2010. I spent way too much time trying to figure out why my groovyc compiled jar, which included a Main-Class: Library header in the manifest, always gave me the same class-not-found error. But compiling an identical (or so I thought) class in Java ran just fine.

After building my jar with the manifest attribute of Main-Class: Library and trying to run the jar like so:

$ java -jar gradleInitTest-1.0.jar

I always received this error:

Error: Could not find or load main class edu.Library

The problem? groovy-all-2.4.4.jar was not on my classpath. As you point out, groovy compiled classes require the groovy jar. As a quick fix I stuck it in a lib directory and ran my jar like so:

java -cp gradleInitTest-1.0.jar:../lib/*:. edu.Library

Worked just fine.

@DustinMarx said...

Joseph,

Thanks for taking the time to let me know this post was helpful and for providing these additional details about your particular situation and how you were able to address it.

Dustin