Skip to content

Groovy Operator Overloading — Custom Operators for Cleaner Code

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Groovy Operator Overloading. We cover key concepts, practical examples, and best practices to help you master this topic.

Groovy operator overloading maps operators like +, *, and [] to specific methods, enabling custom types with intuitive mathematical and collection syntax without boilerplate.

What You'll Learn

  • Operator to method mapping
  • Implementing custom operators
  • Comparison and indexing operators
  • Best practices for overloading

Why It Matters

Operator overloading makes domain types (vectors, matrices, money, dates) behave like built-in types. DodaZIP uses overloaded operators for combining archive paths and calculating compression ratios naturally.

Real-World Use

Mathematical libraries, vector/matrix operations, DSL design, unit conversion, and any domain where algebraic notation improves readability.

flowchart LR
    A["Operators"] --> B["Arithmetic"]
    A --> C["Comparison"]
    A --> D["Indexing"]
    A --> E["Conversion"]
    B --> F["plus, minus, multiply"]
    C --> G["compareTo, equals"]
    D --> H["getAt, putAt"]
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#dbeafe,stroke:#2563eb,color:#1e40af

Operator-Method Mapping

Operator Method Example
a + b a.plus(b) x + y
a - b a.minus(b) x - y
a * b a.multiply(b) x * y
a / b a.div(b) x / y
a ** b a.power(b) x ** y
a[b] a.getAt(b) list[0]
a[b] = c a.putAt(b, c) map[key] = val
a << b a.leftShift(b) list << item
++a a.next() ++count
a == b a.equals(b) x == y
a <=> b a.compareTo(b) x <=> y
a as Type a.asType(Type) val as String

Custom Vector Type

class Vector {
    double x, y

    Vector plus(Vector other) {
        new Vector(x: x + other.x, y: y + other.y)
    }

    Vector minus(Vector other) {
        new Vector(x: x - other.x, y: y - other.y)
    }

    Vector multiply(double scalar) {
        new Vector(x: x * scalar, y: y * scalar)
    }

    double dot(Vector other) {
        x * other.x + y * other.y
    }

    String toString() { "($x, $y)" }
}

def v1 = new Vector(x: 3, y: 4)
def v2 = new Vector(x: 1, y: 2)

println v1 + v2           // (4.0, 6.0)
println v1 - v2           // (2.0, 2.0)
println v1 * 2            // (6.0, 8.0)
println v1.dot(v2)        // 11.0

Custom Money Type

class Money {
    BigDecimal amount
    String currency

    Money plus(Money other) {
        if (currency != other.currency) throw new IllegalArgumentException("Currency mismatch")
        new Money(amount: amount + other.amount, currency: currency)
    }

    Money multiply(BigDecimal factor) {
        new Money(amount: amount * factor, currency: currency)
    }

    int compareTo(Money other) {
        amount <=> other.amount
    }

    boolean equals(Object other) {
        if (!(other instanceof Money)) return false
        amount == other.amount && currency == other.currency
    }

    String toString() { "$currency $amount" }
}

def prices = [
    new Money(amount: 10.00, currency: "USD"),
    new Money(amount: 5.00, currency: "USD"),
    new Money(amount: 7.50, currency: "USD")
]
def total = prices[0] + prices[1] + prices[2]
println total  // USD 22.50

Indexing with getAt/putAt

class Grid {
    private data = [:]
    int width, height

    Grid(int w, int h) { width = w; height = h }

    def getAt(int x, int y) {
        data["$x,$y"]
    }

    void putAt(int x, int y, Object value) {
        data["$x,$y"] = value
    }

    def getAt(Map coords) {
        data["${coords.x},${coords.y}"]
    }
}

def grid = new Grid(10, 10)
grid[3, 4] = "player"
grid[5, 2] = "enemy"

println grid[3, 4]            // player
println grid[x: 5, y: 2]      // enemy
println grid[0, 0]            // null

leftShift Operator

class LogBuffer {
    private entries = []

    LogBuffer leftShift(String entry) {
        entries << "[${new Date().format('HH:mm:ss')}] $entry"
        this
    }

    String toString() { entries.join('\n') }
}

def log = new LogBuffer()
log << "Server started" << "User logged in" << "Request processed"
println log

Comparison Operators

class Priority implements Comparable<Priority> {
    int level
    String label

    int compareTo(Priority other) {
        level <=> other.level
    }

    boolean equals(Object other) {
        if (!(other instanceof Priority)) return false
        level == other.level
    }
}

def low = new Priority(level: 1, label: "low")
def high = new Priority(level: 3, label: "high")
def critical = new Priority(level: 5, label: "critical")

println low < high            // true
println high >= low           // true
println critical > high       // true
println [critical, low, high].sort()
// [low, high, critical]

asType Operator

class Temperature {
    double celsius

    def asType(Class target) {
        if (target == Fahrenheit) {
            return new Fahrenheit(fahrenheit: celsius * 9/5 + 32)
        }
        throw new IllegalArgumentException("Cannot convert to $target")
    }
}

class Fahrenheit {
    double fahrenheit
    String toString() { "${fahrenheit}F" }
}

def temp = new Temperature(celsius: 100)
def f = temp as Fahrenheit
println f  // 212.0F

Common Mistakes

1. Inconsistent equals and compareTo

If equals says two objects are equal, compareTo should return 0. Breaking this contract causes bugs in sorted collections.

2. Mutating objects in operators

plus should return a new instance, not modify this. Operators should be immutable by convention.

3. Forgetting to handle null

Operator methods receive null arguments. Check for null before dereferencing.

4. Overloading too many operators

Only overload operators where the meaning is obvious. matrix * matrix is clear. customer * product is not.

5. Unsupported operations

If subtract doesn't make sense for your type, don't implement minus. An error is better than a confusing result.

Practice Questions

1. What method does the << operator map to?

leftShift. Example: list << item calls list.leftShift(item).

2. How do you implement custom array indexing?

Define getAt and putAt methods. The parameters can be positional or use maps for named indices.

3. What must you implement for == to work correctly?

Override equals(Object) and hashCode(). Groovy's == always calls equals.

4. Can you overload ?: (Elvis) operator?

No. The Elvis operator cannot be overloaded in Groovy.

Challenge: Create a ComplexNumber class with full operator overloading for arithmetic and comparison.

FAQ

{{< faq question="Is operator overloading unique to Groovy?" >}} No. C++, Python, Kotlin, and Scala also support it. Java does not. {{< /faq >}}

{{< faq question="Does operator overloading work with @CompileStatic?" >}} Yes. The method calls are resolved at compile time based on the declared types. {{< /faq >}}

{{< faq question="Can I overload assignment (=) operator?" >}} No. The assignment operator cannot be overloaded in Groovy. {{< /faq >}}

{{< faq question="What is the performance impact of operator overloading?" >}} None. Operators are just method calls. The bytecode is identical to calling the method directly. {{< /faq >}}

{{< faq question="Can I change the precedence of operators?" >}} No. Operator precedence is fixed in the language and cannot be modified. {{< /faq >}}

Mini Project

Create a Duration class that supports arithmetic and comparison for time intervals.

class Duration {
    long milliseconds

    static Duration ofSeconds(long s) { new Duration(milliseconds: s * 1000) }
    static Duration ofMinutes(long m) { new Duration(milliseconds: m * 60000) }

    Duration plus(Duration other) {
        new Duration(milliseconds: milliseconds + other.milliseconds)
    }

    Duration multiply(int times) {
        new Duration(milliseconds: milliseconds * times)
    }

    int compareTo(Duration other) {
        milliseconds <=> other.milliseconds
    }

    String toString() {
        "${milliseconds / 1000}s"
    }
}

def fiveSec = Duration.ofSeconds(5)
def twoMin = Duration.ofMinutes(2)

println fiveSec + twoMin  // 125s
println twoMin * 3        // 360s
println fiveSec < twoMin  // true

What's Next

Now that you understand operator overloading, proceed to Groovy scripting.

Topic Description Link
Scripting Scripts and bindings {{< ref "25-scripting" >}}
AST transformations Compile-time Metaprogramming {{< ref "26-ast-transformations" >}}
Builders Markup Builder patterns {{< ref "09-builders" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro