Learn Groovy - Core Concepts & Language Architecture
Episode 2 of 23

Learn Groovy - Core Concepts & Language Architecture

This episode dissects Groovy's architecture: program structure, the execution model on the JVM, compilation into bytecode, and two-way interoperability with Java. You will also run Groovy from within a Java application using GroovyShell.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

After understanding Groovy's history and position in the JVM ecosystem, it's time to dissect the language architecture — how Groovy works behind the scenes. This understanding matters because it explains why Groovy can be dynamic and, at the same time, compatible with Java.

Episode 2 covers Groovy's program structure, its execution model on the JVM, the bytecode compilation process, and two-way interoperability between Groovy and Java. By the end of the episode you'll be able to run Groovy code from within a Java application and vice versa.

Groovy Program Structure

Script as the Execution Unit

Groovy's program structure depends on how you run it. There are three main forms:

  • Script: a .groovy file without a class declaration, executed directly from top to bottom.
  • Class: a .groovy file containing a Groovy class declaration, similar to a Java file.
  • Mixin: a combination of both, where one file contains a class and also free statements outside the class.

When Groovy runs a script, it automatically wraps it into a class named Script, and every statement becomes the body of a run method. Variables declared at script level become properties of that class.

Variable Binding and Run

This model creates an interesting property: variables defined in one part of a script remain accessible in other parts, because everything lives in a single run scope. Here's an example of a simple script structure:

Groovy script structure
def nama = "Arman"
def umur = 25
 
def perkenalan() {
    return "Nama: ${nama}, Umur: ${umur}"
}
 
println perkenalan()

The script above defines two variables and one method, then calls it. return "Nama: ${nama}, Umur: ${umur}" leverages GString to interpolate variable values — a topic we'll dive into in episode 3.

Execution Model on the JVM

Compilation into Bytecode

Groovy has two main execution paths. First, Groovy compiles code into bytecode with groovyc, producing .class files that can be run by the JVM like ordinary Java classes. Second, a script can be run directly with the groovy command, which performs compilation automatically behind the scenes.

Compile a script into classes
groovyc halo.groovy
ls *.class

The groovyc halo.groovy command produces one or more .class files in the same directory. Those files can be run with java if a main class is found, or bundled into a larger Java project.

Dynamic Dispatch

Unlike Java, Groovy compilation preserves dynamic dispatch. When a method is called, Groovy looks up the appropriate implementation at runtime, not at compile time. This is what enables metaprogramming — method behavior can be changed or added after compilation.

As a result, Groovy bytecode is larger and slightly slower than pure Java bytecode. Episode 16 will cover this trade-off in depth, including how to close it with @CompileStatic.

Groovy as a Scripting Language

Execution Engine and GroovyShell

For scripting needs inside an application, Groovy provides GroovyShell. This is the primary API for evaluating Groovy expressions or scripts from within Java code. Here's an example:

Evaluate a script with GroovyShell
def shell = new GroovyShell()
def hasil = shell.evaluate("1 + 2 * 3")
println hasil

shell.evaluate("1 + 2 * 3") executes a Groovy string and returns its result, which is 7. new GroovyShell() creates an evaluation engine instance that can be reused repeatedly with the same variable binding.

GroovyConsole and Groovysh

For interactive exploration, Groovy provides two tools: groovyConsole, which is GUI-based, and groovysh, which is terminal-based. Both use the same engine as GroovyShell, so the results you see in the REPL will be consistent with production behavior.

Interoperability with Java

Calling Java Libraries from Groovy

Because it runs on the JVM, Groovy can use any Java library without adapters. Importing Java packages works directly, including access to enums, static methods, and generic types.

Using Java libraries from Groovy
import java.time.LocalDate
 
def hariIni = LocalDate.now()
println "Tanggal hari ini: ${hariIni}"

import java.time.LocalDate shows that Groovy's import syntax is identical to Java. LocalDate.now() is called directly, and the result can be interpolated into a GString without boilerplate.

Running Groovy from a Java Application

Interoperability also works in the opposite direction. Groovy jars implement GroovyObject and are compatible with Java's reflection mechanism, so Java applications can use Groovy classes directly. For dynamic script evaluation, Java uses GroovyShell:

GroovyShell from Java code
import groovy.lang.GroovyShell;
 
public class Main {
    public static void main(String[] args) {
        GroovyShell shell = new GroovyShell();
        Object hasil = shell.evaluate("2 + 2");
        System.out.println(hasil);
    }
}

To run the Java code above, make sure groovy-4.0.24.jar is on the classpath. shell.evaluate("2 + 2") inside the Java code calls the same Groovy engine, proving that Groovy and Java live in one JVM process without restrictions.

Choice: Groovy Console vs Embedded

There are two patterns for using Groovy in a Java application:

  • Compile-time: Groovy classes are compiled together with the Java project, resulting in type-safe, fast code.
  • Runtime: scripts are evaluated with GroovyShell or GroovyClassLoader, flexible but slower and requiring security attention.

We'll revisit the runtime pattern in episode 20, especially the sandboxing aspects for scripts that come from untrusted sources.

Closing

Episode 2 explained how Groovy works internally: scripts are wrapped into a Script class, compiled into JVM bytecode, preserve dynamic dispatch at runtime, and interact with Java in both directions through GroovyShell.

The key takeaways:

  • Groovy scripts are automatically wrapped into a Script class by the engine.
  • groovyc produces .class bytecode that Java can use.
  • Groovy uses dynamic dispatch, so method behavior can change at runtime.
  • GroovyShell is the primary API for evaluating scripts from a Java application.
  • Importing and calling Java libraries works seamlessly without adapters.
  • Choose the compile-time pattern for performance, the runtime pattern for flexibility.

In episode 3 next, we'll discuss basic syntax and data types — dynamic variables with def, primitive types, strings, lists, maps, GString, multi-line strings, and string operators. These are the basic vocabulary used in every episode that follows.