U-Groovy

Published: (January 1, 2026 at 10:58 AM EST)
6 min read
Source: Dev.to

Source: Dev.to

Overview

I have been working with Groovy in Jenkins pipelines for an extended period of time.
Now I’m moving more into the Java world, so it’s a good time for a short summary of what this language can do.

In my view Groovy is a U‑language: unknown, underrated, and unnoticed. Many people have heard of it, but only a few know how handy and universal it really is.

In general, Groovy is like Java – it runs on the JVM and understands pure Java code, which lets Java developers start using it instantly.
Unlike Java, Groovy can be used in almost any programming paradigm, allowing very flexible structuring of the codebase. Below are a few simple examples that illustrate this flexibility.

A. Imperative Programming

Imperative programming describes how a program performs step‑by‑step. It is the most common way to write computer programs.

1. Scripting Language

Groovy can be used as a plain scripting language, much like Bash:

println "this is just plain scripting"
a = 1
b = 2
println a + b

2. Object‑Oriented Programming with Dynamic Types

You can work in an OO style while using dynamic typing:

class Car {
    def regNumber = "123AB"
    def numberOfStarts = 0
    def weight = 2

    def start() {
        numberOfStarts++
        println "running"
    }

    def stop() {
        println "stopping"
    }

    def getRegNumber() {
        return regNumber
    }

    // Example of a dynamic getter (not required for the demo)
    def getNumberOfStarts() {
        return numberOfStarts
    }
}

def car = new Car()
car.start()
car.start()
assert car.getNumberOfStarts() == 2
println car.getRegNumber()

car = "this is car"
println car

When static typing is overkill you can simplify the code and avoid it.
Dynamic typing also means you can assign values of different types to the same variable (def car). A method can return different types depending on runtime conditions.

3. Object‑Oriented Programming with Static Types

If you prefer a more Java‑like style, you can use static types (note the lack of semicolons):

class Plane {
    String regNumber = "123AB"
    int weight = 20
    int numberOfFlights

    void fly() {
        numberOfFlights++
        println "flying"
    }

    void land() {
        println "landing"
    }

    String getRegNumber() {
        return regNumber
    }

    int getNumberOfFlights() {
        return numberOfFlights
    }
}

Plane plane = new Plane()
plane.fly()
plane.fly()
assert plane.getNumberOfFlights() == 2
println plane.getRegNumber()

B. Declarative Programming

Declarative programming describes what the result should be, not how to achieve it.

1. Groovy‑Based Declarative Syntax

You can create custom declarative DSLs (Domain‑Specific Languages) in Groovy:

println "declarative programming"

def surroundedWithDashes(closure) {
    println "-------------"
    closure.call()
    println "-------------"
}

def show(closure) {
    println closure.call()
}

def sumOfNumbers = { a, b -> a + b }

surroundedWithDashes {
    show { sumOfNumbers(3, 6) }
    show { sumOfNumbers(1, 2) }
    show { sumOfNumbers(5, 10) }
}

After the DSL is defined, calling surroundedWithDashes { … } automatically produces the desired layout.

2. Functional Programming

Groovy supports functional programming concepts such as pure functions, function chaining, memoization, and currying. Most tasks can be expressed with just three methods: collect, findAll, and inject.

println "functional programming"

def data = [1, 5, 8, 3, 5]
def result = data
        .collect { it + 10 }          // add 10 to each element
        .findAll { it > 13 }          // keep only values > 13
        .toUnique()                   // remove duplicates

assert result == [15, 18]

Benefits come not only from chaining but also from functional features like:

  • Memoization – caching the result of a function for given inputs.
  • Currying – creating new functions by fixing some arguments of a generic one.

C. Metaprogramming

Metaprogramming is arguably Groovy’s hallmark. It lets you modify or generate code at compile time or at runtime.

1. Compile‑Time Metaprogramming

You can generate code during compilation using AST (Abstract Syntax Tree) transformations:

println "compile-time metaprogramming"

@Log
class Car2 {
    def regNumber
    def numberOfStarts = 0
    def weight = 2

    @NullCheck
    Car2(regNumber) {
        this.regNumber = regNumber
    }

    def start() {
        numberOfStarts++
        log.info "log: running"
    }

    def stop() {
        log.info "log: stopping"
    }

    String getRegNumber() {
        return regNumber
    }
}

try {
    new Car2().start()
} catch (error) {
    println "error was caught: ${error}"
}

new Car2("12345").start()

@Log injects a logger, while @NullCheck adds a null‑check to the constructor. No explicit null‑checking code is needed in the source.

2. Runtime Metaprogramming

At runtime you can intercept, inject, or even synthesize methods and properties. This capability makes Groovy extremely flexible for building DSLs, mocking objects in tests, or adapting existing APIs on the fly.

Example

@Log
class Car3 {

    def regNumber
    def numberOfStarts = 0
    def weight = 2

    @NullCheck
    Car3(regNumber) {
        this.regNumber = regNumber
    }

    def start() {
        numberOfStarts++
        log.info "log: running"
    }

    def stop() {
        log.info "log: stopping"
    }

    def getRegNumber() {
        return regNumber
    }

    def methodMissing(String name, args) {
        log.info "log: called missing method: ${name} with arguments: ${args}"
    }

}

def car3 = new Car3("12345")
car3.someUnexistingMethod("with parameter")
car3.metaClass.additionalMethod = { param ->
    log.info "log: called added method: additionalMethod with parameter: ${param}"
}
car3.additionalMethod("paramValue")

After the built‑in method methodMissing is implemented, it will be called whenever an undefined method of the class is invoked.
The additionalMethod can be added to the class at runtime and used successfully.

Why use this feature?

It is very useful for:

  • Testing – especially when mocks are needed.
    See my related article: Mocking with Groovy

  • Advanced tasks – such as writing testing frameworks.
    Example: a simplified Jenkins testing framework on GitHub – Jenkinson

Take‑away

  • Groovy runs on the JVM and understands pure Java code.
  • It supports imperative, declarative, functional, and metaprogramming styles.
  • You can choose dynamic or static typing (or a mix of both).
  • Its DSL‑building and AST‑transformation capabilities let you tailor the language to your domain.

Whether you’re writing quick scripts for Jenkins pipelines or building large, type‑safe applications, Groovy offers a versatile toolbox that many Java developers overlook. Happy coding!

Summary

These are simple examples of the many ways you can write Groovy code. I only scratched the surface, but I hope the examples spark curiosity and interest in the language.

I’m still learning Groovy myself, but I have used all of the features shown here to some extent. For comprehensive documentation, see:

  • Groovy Documentation:

If you haven’t explored the docs yet, they’re definitely worth a read—there are tons of additional language features.

In my opinion, this flexibility is Groovy’s biggest advantage when choosing a language for a task or project, and it also makes a great teaching tool.

I hope you find this short article useful.

Back to Blog

Related posts

Read more »

Introduction to Java

How does Java code written by humans get executed by machines? It uses a translation system JDK → JRE → JVM → JIT to convert the programming language to machin...

Ruby 파일 처리와 시스템 명령

파일 열기와 읽기 ruby File.open'p014constructs.rb', 'r' do |f1| while line = f1.gets puts line end end 파일 쓰기 ruby File.open'test.rb', 'w' do |f2| f2.puts 'Created by...