December 28, 2022

Scripting With Groovy in Java Applications

Java Application Development
Developer Productivity

In this post I'd like to present you with the case for using an alternative JVM scripting language - Groovy - for extending your Java application. We'll start out with a few cases where scripting in Java apps with Groovy may be the right move, then give an example of how to embed a script with Groovy in Java.

What is Groovy?

Groovy is an object-oriented programming language that is both static and dynamic. It can be used both as a programming language and as a scripting language for Java. Groovy is a pleasant language to develop in, as it reduces huge amounts of boilerplate code including no semi colons, no getters/setters, type inference, null safety, elvis operators and much, much more.

That’s not to say you’re not permitted to use Java notation if you so choose, though. Interestingly, most Java code will compile in groovy so it has a low bar for entry into the language.  It’s become a mature choice that developers trust when Java verbosity hurts and dynamic typing isn’t an issue.

A Case for Scripting With Groovy in Java Applications

So why would I want to move some pieces of logic into (Groovy) scripts instead of implementing the same in Java classes? One obvious reason for this is the customization of application business logic. Again, why? Because sometimes there's a need to deliver code faster than it is possible for the whole application.

An externalized script can be seen as a minimalistic business rule definition as opposed to full-blown BRMS implementations. This kind of approach could be viewed as a poor-man's plugin system, where it would be possible to implement plugins in a scripted manner.

Another case for scripting is when there's a need to interactively control the application. For this purpose, scripting has become widely adopted in Java middleware: Jython scripts in IBM WebSphere AS and Oracle WebLogic are the well-known examples.

Embedding Groovy in Java

Groovy is a great language for embedding into Java applications. The Groovy API provides us with GroovyShell class which is used as an entry point for Groovy scripts execution.


GroovyShell shell = new GroovyShell();
shell.evaluate("println 'Hello from Groovy script!'");


By executing the lines above obviously the program prints "Hello from Groovy script!" line.

What if we need it to be implemented as a deferred callback? In that, we don’t want to execute the Groovy code as it is evaluated, rather execute the pieces of the script based on decisions we make in the application later on. In that case we could move the code into a dedicated method and evaluate the script. The result of the script evaluation:


GroovyShell shell = new GroovyShell();
Object script = shell.evaluate("def sayHello() { " + 
    "println 'Hello from Groovy script!' }");


The problem now is that the script variable is null. We didn't return anything from the script! Obviously, an attempt to call the foo method via reflection would result in NullPointerException in this case.

Actually, we should explicitly return the result of the script at the very last line:


Object script = shell.evaluate("def sayHello() { " + 
    "println 'hello from Groovy script!' }; this");


This variable represents an instance of class Script1 for the current example. Script1 is an automatically generated name for the piece of code that we have evaluated with GroovyShell. For every new evaluation GroovyShell with generate a new class name by incrementing the suffix - Script2, Script3, etc.

So now we could actually call the sayHello() method via reflection:


Object script = shell.evaluate("def sayHello() { " + 
    "println 'Hello from Groovy script!' }; this");
Method m = script.getClass().getMethod("sayHello");
m.invoke(script);


Next, we could make use of interfaces for the better design. The script evaluation result could then be hidden behind a dynamic proxy. Let's assume that we have an Engine interfaces and it is considered to be a pluggable component in our application:


public interface Engine {
   void start();
   void stop();
}


And the following is a Groovy script that implements the Engine interface by convention:


//engine.groovy
def start(){
   println "Start the engines!"
}
def stop(){
   println "Stop at once!"
}
this


At the very last line of the script, again, we can see this reference declared as a result for the script evaluation. So we will be able to call the methods implemented in the script via this reference. Then the following snippet maps the script to the Engine interface:


final Object e = shell.evaluate(new File("engine.groovy"));
Engine engine = (Engine) Proxy.newProxyInstance(app.getClass().getClassLoader(),
         new Class[]{Engine.class},
         new InvocationHandler() {
           @Override
           public Object invoke(Object proxy, Method method, Object[] args) 
             throws Throwable {
             Method m = e.getClass().getMethod(method.getName());
             return m.invoke(e, args);
           }});

engine.start();  // Start the engines!
engine.stop();  // Stop at once!


This is a very simplified example as we assume that the methods do not take any parameters and the only thing we need to do is find it by name, and in real life we would definitely have to handle method invocation errors as well. But I guess you get the idea - the dynamic proxy serves as a gate into the script so that the rest of our application wouldn't have to deal with reflection and script evaluation.

Get the latest data and analysis on the state of the Java ecosystem

Learn about emerging trends, changes, and more in the Java community with the 2022 Java developer Productivity Report.

Download the Free Report

Re-Evaluating the Groovy Script

As we can see, the script is evaluated dynamically, and we definitely could evaluate it again when needed. This leads us to the idea of updating the certain pieces of our logic on-the-fly, without taking the application down.

Here's an example that demonstrates the script re-evaluation:


  new Thread(new Runnable() {
    @Override
    public void run() {
      try {
        execute();
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    private void execute() throws Exception {
      final File file = new File("engine.groovy");
      long timestamp = file.lastModified();
      GroovyShell shell = new GroovyShell();
      Object app = shell.evaluate(file);
      Engine e = getEngine(app);
      while (true) {
        if (timestamp < file.lastModified()) {
          timestamp = file.lastModified();
          app = shell.evaluate(file);
          e = getEngine(app);
        }

        e.start();
        e.stop();

        Thread.sleep(5000);
      }
    }

    private Engine getEngine(final Object app) {
      return (Engine) Proxy.newProxyInstance(app.getClass().getClassLoader(),
        new Class[]{Engine.class},
        new InvocationHandler() {
          @Override
          public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
            Method m = app.getClass().getMethod(method.getName());
            return m.invoke(app, args);
          }
        });
      }
    }).start();


The script is evaluated in the the loop we just call start() and stop() methods via the Engine interface. Whenever the script file is modified, we can detect that by timestamp and re-evaluate the script again via GroovyShell.

Final Thoughts

Scripting equips us with enormous flexibility in regards to implementing the business logic of the application and customizations that we can implement if required.

Combined with powerful DSL abilities of Groovy we could create very nice, readable, technically externalized business rules for our Java application. This approach results in a win-win situation from both the technical and business POV: it is technically simple and you can achieve the business goal by being able to update application logic quickly without bringing the application down even in live environments.

Accelerate Development Further

Speaking of updating applications quickly, did you know that developers can skip the rebuild and redeploy steps entirely with JRebel? Take it for a spin with a free 14-day trial.

Try JRebel for Free

This article was originally published in 2013 and has been updated for relevance.