Why You Should Care About Groovy

If you know anything about me, you probably know that I'm a big fan of Groovy.  But why?  I've never really addressed that question head on, I don't think, so I'm going to do that here (prompted by a comment from David McGuigan on my CFGroovy 1.0 post).

First, Groovy is a dynamic language for the JVM, in much the same vein as CFML, Jython or JRuby.  By "dynamic" I mean that things get figured out at runtime.  Contrast this with a static language like Java, where everything is wired together at compile time (aside from reflection).  For example, in order to reference a variable in Java, the variable has to be declared in the source and available at compile time.  With a dynamic language, the variable only has to be there when you reference it.  It doesn't have to exist before then.

Groovy is also an "essential" language (again like CFML, Python, Ruby) in that "ceremonious" constructs are minimized.  In Java, you have to do a lot of boilerplate/ceremonious coding (handling checked exceptions, types, manual decoration, etc.).  With an essential language that is minimized as much as possible.  Here's a simple example of reading a file with Groovy:

text = new File("/path/to/file.txt").text
println(text)

and in CFML:

<cfset text = fileRead("/path/to/file.txt") />
<cfoutput>#text#</cfoutput>

and here it is in Java:

import java.util.Scanner;

String text = null;
Scanner scanner = new Scanner(new File("/path/to/file.txt"));
try {
  text = scanner.useDelimiter("\\Z").next();
} finally {
  scanner.close()
}
System.out.println(text);
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class JavaTest {
  public static void main(String[] args) {
    BufferedReader r = null;
    try {
      r = new BufferedReader(new FileReader("/path/to/file.txt"));
      StringBuffer sb = new StringBuffer();
      while (r.ready()) {
        sb.append(r.readLine()).append("\n");
      }
      String text = sb.toString();
      System.out.println(text);
    } catch (IOException ioe) {
      // well crap
      System.err.println("got an IOException: " + ioe);
      if (r != null) {
        try {
          r.close();
        } catch (IOException ioe2) {
          // oh well
        }
      }
    }
  }
}

Ick.

With the Scanner class (which I was not aware of until a commenter mentioned it) the Java example isn't terribly worse than the Groovy and CFML examples, so I retract my previous "ick" statement.  The Java is certainly less direct, however.

So we want to use Groovy or CFML instead of Java for this sort of thing, but why Groovy in particular?  Unlike CFML (or Jython, JRuby, etc.) Groovy is designed as something of a language extension to Java, rather than a totally separate language in it's own right.  To put that another way, Groovy builds on not just the JVM, but also on the Java language itself to a large degree.  It makes many things enormously simpler compared to Java, but using a familiar syntax.  In fact, nearly 100% of Java syntax is also valid Groovy.

That buys us several really nice features.  First, Groovy is easy to learn if you've done any Java.  Second, it means that the language constructs map back to Java constructs almost directly, which means accessing Java code from Groovy is a snap.  Finally, it means you can use Groovy to create "Java" constructs, and since Groovy is dynamic, you can do it on the fly.  If you've ever tried to leverage Java libraries from CFML, you know the pains of createObject, native arrays, and null.  Here's an example of creating a URL[] (a Java array of URLs) from comma-delimited string of filenames in CFML:

<cfset urlClazz = createObject("java", "java.net.URL").init("http://barneyb.com/").getClass() />
<cfset Array = createObject("java", "java.lang.reflect.Array") />
<cfset path = "/path/to/lib.jar,/path/to/otherlib.jar" />
<cfset urlArray = Array.newInstance(urlClazz, listLen(path)) />
<cfset i = 0 />
<cfloop list="#path#" index="item">
  <cfset Array.set(urlArray, i, createObject("java", "java.io.File").init(item).toURL()) />
  <cfset i = i + 1 />
</cfloop>

and in Groovy:

path = "/path/to/lib.jar,/path/to/otherlib.jar".tokenize(",")
urlArray = new URL[path.size]
path.eachWithIndex { it, i ->
  urlArray[i] = new File(it).toURL()
}

As you can see, Groovy's "closeness" to Java makes dealing with Java constructs enormously easier.  Of course, if you're not using Java libraries, this benefit is of minimal benefit. The Groovy example also illustrates a closure and the eachWithIndex iterator.  This is where Groovy really shines.  You can write incredibly concise code without sacrificing readability.

Groovy also allows you to create classes on the fly in your scripts, something that CFML has no way of providing.  Need a Comparator?  Or perhaps a Runnable?  You can create those inline, no separate files, no compilation, nothing.  As an example, let's say I have a List of Maps (or an array of structs in CFML) and I want to sort them by the "letter" Map/struct key.  Here's some CFML to do it:

<!--- myArray is an array of structs, each with a "letter" key --->
<cfset keys = "" />
<cfloop from="1" to="#arrayLen(myArray)#" index="i">
  <cfset keys = listAppend(keys, myArray[i].letter & "~" & i) />
</cfloop>
<cfset newArray = [] />
<cfloop list="#listSort(keys, 'textNoCase')#" index="key">
  <cfset arrayAppend(newArray, myArray[listLast(key, "~")]) />
</cfloop>
<cfset myArray = newArray />

CFML has no way of sorting an array of complex objects, so we have to fake it by building a collection of simple objects (a list of Strings, in this case), sorting that, and then reversing it back to the complex objects.  This also has a very undesirable (though non-obvious) side effect: it changes the myArray reference, not just the myArray object.  With CFML's pass-by-value semantic for arrays it's not as big a deal as it might otherwise be, but still a nasty consequence that can yield some really insidious bugs.   You can beat it by replacing the last line with this:

<cfset myArray.clear() />
<cfset myArray.addAll(newArray) />

This keeps the myArray reference intact, but requires a bit of knowledge about the Java Collections framework.

Here's the same example in Groovy:

// myArray is a List of Maps, each with a "letter" key
Collections.sort(myArray, {o1, o2 ->
  o1.letter.compareTo(o2.letter)
} as Comparator)

Here I'm using a closure (the part in blue) as a Comparator instance and using the Collections.sort method.  Without a comparator, there's no way to use this method, which is why the CFML has do the whole thing manually.  The example  requires a little knowledge of the Java Collections framework, but the readability and maintainability of the code is enormously better, so it'll cover the cost of learning very quickly.  And as you saw, in order to get the CFML code to actually do what you want (sort the array, not create a new array in sorted order) you end up having know about the Java Collections framework anyway.

Finally, Groovy also provides a rich metaprogramming environment.  I'm not going to show any code, but simply put, metaprogramming is a way of having code effect the program while it's running.  To put that another way, programming effects data, metaprogramming effects the program itself.  For example, you can add new methods to objects (or whole classes of objects) at runtime.  This is the acme for dynamic languages – being so dynamic the code itself is mutable at runtime.

What about downsides?

First, Groovy is a new language to learn; no getting around that.  You have to learn new syntax and new idioms, and keep them sorted from other languages' syntax and idioms.

Second, if you have a significant investment in another language, leveraging Groovy requires a integration features.  With Java it's simple – the Groovy compiler provides that.  I struggled with Jython integration, though I'll admit I didn't spend much time on it.  For CFML, I've invested significant effort in building CFGroovy to address this issue, but it still has limitations.

Third, Groovy's focus on dynamic execution and metaprogramming has performance implications for certain types of operations.  If you're writing a mathematics library, for example, Groovy is a horrible choice.  As such, unless you development needs are constrained to high level concepts, Groovy is probably not the best choice for a one-size-fits-all language.  Groovy is not unique in this way, of course.  CFML, Jython and JRuby all seem to have lower performance penalties, but at the expense of "dirtier" Java integration and less comprehensive dynamic capabilities.  Different language / different focus.

In conclusion, Groovy is not a panacea.  It's nothing more than a potential tool to have in your developer toolbox.  However, when integrated into an existing JVM-based environment, or as a foundation language for new development, it can be an incredibly powerful tool.

21 responses to “Why You Should Care About Groovy”

  1. Sami Hoda

    Nice post!

  2. David McGuigan

    Spectacularly informative post. Exactly what I needed. Even included a microdiscussion of performance comparisons! Mad, no, furious props.

  3. John Allen

    Just a brilliant post. BarneyB is smart.

  4. Henry Ho

    Is there a good site for looking up Groovy syntax / API? Maybe a… Groovy cheatsheet?

    Thanks!

  5. Kevin Burke

    So you like terseness, huh? How about this, then:

    urls = "/path/to/lib.jar,/path/to/otherlib.jar".tokenize(",").inject([]){ urls, url -> urls.add(new File(url).toURL()); return urls; }

    Groovy is fun indeed ;)

  6. Henry Ho

    @barneyb, awesome, thanks.

  7. Sean Nieuwoudt

    Thanks for the post,

    I havnt been following Groovy all that much to be honest, should probably start.

    In your experience, what would the barrier to entry be for someone who is a PHP developer for instance?

  8. bob

    All your saying is "scripting in java would be nice". "And if it could leverage standard APIs great!"

    Which it obviously can. So what.

    Scripting in Java is out there besides groovy without creating a new for another language.
    And besides, groovy is slower than all the other except maybe jruby.

    So why care?

  9. Ivo

    Comparisons like these are dishonest. As the Scala guys say: you could consider the Scala jar to be 'just another' Java library. In the same sense, Groovy is a library and behind that single line of Groovy is a *lot* of code. If you are going to compare Groovy to Java, a fair comparison would at least involve the 'regular' Java libraries, like Apache Commons IOUtils to read a file.

  10. Rodrigo Asensio

    The file printout sample that you did on java is not true, too many catchs. What about the groovy exception management ? It shows you're devoted to groovy.
    But anyways, after my critic I will tell you I will start with groovy as a "on the fly" testing tool for my apps. I really want to learn it. Seems a really easy to learn scripting.

    thanks for the article.
    Regards

  11. Thomas Messier

    Excellent post! Thanks for passing on all this info. Can't wait to try the latest CFGroovy, haven't had time yet.

  12. Tim Yates

    Your java example is a bit unfair, as I believe this would work equally:

    String file = new Scanner( new File( "/path/to/file.txt" ) ).useDelimiter( "\\Z" ).next() ;

  13. Rodrigo Asensio

    agree with the Scanner sample

  14. Rodrigo Asensio

    yeah, I think ignorance was the main misunderstanding here. But anyways, Groovy seems promising and must take his place as py does.

  15. haraakiri

    Java code which uses Scanner, now it seems to throw unchecked exception on error rather than checked exceptions which is what Java programmers expect. Who closes the file? Does scanner does it? Why don't we write better groovy code using RAII, which is so better compared to java's finally