ZetCode

Groovy Interfaces and Inheritance

last modified October 15, 2026

Interfaces define a contract: a set of methods that an implementing class must provide. Inheritance lets a class reuse and extend another class. Groovy supports both in the same way as Java, and adds named and default parameters that make method calls easier to read. This tutorial covers interfaces, method overriding with super, named parameters, and default parameter values.

The basics of abstract classes, @InheritConstructors, and map-style constructors are covered in the Groovy classes tutorial. Default parameters for simple functions are covered in the Groovy functions tutorial. This article focuses on the interface and parameter patterns that build on them.

Defining and Implementing an Interface

An interface is declared with the interface keyword. Its methods have no body, so each implementing class must supply them. A class lists the interfaces it implements after the implements keyword.

Interfaces.groovy
interface Shape {
    double area()
    String name()
    default String describe() {
        "${name()} with area ${String.format('%.2f', area())}"
    }
}

interface Scalable {
    void scale(double factor)
}

class Circle implements Shape, Scalable {
    double radius
    Circle(double radius) { this.radius = radius }
    double area() { Math.PI * radius * radius }
    String name() { 'Circle' }
    void scale(double factor) { radius *= factor }
}

def c = new Circle(2)
println c.describe()
c.scale(2)
println c.describe()
println c instanceof Shape
println c instanceof Scalable
println Shape.isAssignableFrom(Circle)

Shape declares two abstract methods, area and name, and one method with a body, describe. The Circle class implements both Shape and Scalable, so it must provide the abstract methods of both.

default String describe() {
    "${name()} with area ${String.format('%.2f', area())}"
}

A default method in an interface has an implementation. Classes that implement the interface inherit it and can override it. Because describe calls area() and name(), its result changes with the state of each object.

println c instanceof Shape
println Shape.isAssignableFrom(Circle)

instanceof checks the type of an object at runtime. The isAssignableFrom method of the Class object does the same check for classes, which is useful when the types are only known at runtime.

Groovy does not require the public modifier here, because methods are public by default, both in interfaces and in classes.

$ groovy Interfaces.groovy
Circle with area 12.57
Circle with area 50.27
true
true
true

Inheritance and Method Overriding

A subclass extends a superclass with the extends keyword. It can override a method by declaring a method with the same signature. The super keyword calls the superclass version, either inside an overridden method or in the constructor.

Inheritance.groovy
class Animal {
    protected String name
    Animal(String name) { this.name = name }
    String sound() { '...' }
    String speak() { "$name says ${sound()}" }
}

class Dog extends Animal {
    Dog(String name) { super(name) }
    @Override
    String sound() { 'Woof' }
    @Override
    String speak() { super.speak() + '!' }
}

def animals = [new Animal('Generic'), new Dog('Rex')]
animals.each { println it.speak() }
println Dog.superclass.simpleName
println new Dog('Max') instanceof Animal

Animal.speak calls sound(), which is dispatched at runtime. When speak runs on a Dog, the overridden sound returns 'Woof'. This is polymorphism: the same call produces different results depending on the object's actual class.

Dog(String name) { super(name) }

The super(name) call must be the first statement in the constructor. It passes the name to the Animal constructor, which sets the protected field.

String speak() { super.speak() + '!' }

super.speak() runs the Animal version of speak, and the Dog version adds an exclamation mark. Extending a method this way avoids copying its code into the subclass.

A class can implement several interfaces, but it can extend only one class. Groovy also supports traits, which combine interface-style contracts with reusable method bodies; they are covered in the Groovy traits tutorial.

$ groovy Inheritance.groovy
Generic says ...
Rex says Woof!
Animal
true

Named Parameters with a Map

Groovy lets callers pass arguments by name. When a method has a single Map parameter, a call such as connect(host: 'x') is collected into that map. The method reads each key with get, which accepts a default value for missing keys.

NamedParams.groovy
def connect(Map args = [:]) {
    def host = args.host ?: 'localhost'
    def port = args.get('port', 8080)
    def ssl = args.ssl ?: false
    "$host:$port ssl=$ssl"
}

println connect()
println connect(host: 'example.com', ssl: true)
println connect(port: 443, host: 'api.example.com')

The parameter has the default [:], an empty map. Without it, a call with no arguments would pass null and the first property access would fail. The order of named arguments does not matter.

def port = args.get('port', 8080)

args.get returns the value for port or the fallback 8080 only when the key is missing. The Elvis operator ?: is different: it also replaces falsy values such as 0, false, or an empty string. Use get when those values are valid input.

The map approach accepts any key, so a misspelled name such as prot: is silently ignored. Passing a map with unknown keys is also allowed. If strict checking is required, validate the keys yourself or use the annotation described in the next section.

$ groovy NamedParams.groovy
localhost:8080 ssl=false
example.com:8080 ssl=true
api.example.com:443 ssl=false

Named Parameters with @NamedVariant

The @NamedVariant annotation generates a method that takes named arguments, and @NamedParam describes each parameter. It can mark a parameter as required and give it a default value, and it checks that the required names are present.

NamedVariant.groovy
import groovy.transform.NamedVariant
import groovy.transform.NamedParam

@NamedVariant
def createUser(@NamedParam(required = true) String name,
               @NamedParam int age = 18,
               @NamedParam String role = 'user') {
    "$name ($age, $role)"
}

println createUser(name: 'Ann')
println createUser(role: 'admin', name: 'Bob', age: 40)

The method has three parameters, but the caller only has to provide name. age and role fall back to their defaults, and the arguments can be given in any order.

@NamedParam(required = true) String name

Marking a parameter as required turns a missing argument into an error that names the missing key, instead of a null value that fails later.

Calling createUser(age: 3) without name fails with Missing required named argument 'name'. Use @NamedVariant for public methods where the caller should get a clear message, and the plain map approach for quick internal helpers.

$ groovy NamedVariant.groovy
Ann (18, user)
Bob (40, admin)

Default Parameter Values

A parameter with a default value can be omitted by the caller. Defaults are written with = after the parameter name. Arguments are matched by position, so parameters with defaults must come after the required ones.

Defaults.groovy
def greet(String name, String greeting = 'Hello', String punct = '!') {
    "$greeting, $name$punct"
}

println greet('Alice')
println greet('Bob', 'Hi')
println greet('Carol', 'Hey', '?')

class Point {
    int x, y
    Point(int x = 0, int y = 0) { this.x = x; this.y = y }
    String toString() { "($x, $y)" }
}

println new Point()
println new Point(5)
println new Point(5, 7)

Each call supplies as many arguments as it needs. Groovy compiles one method for each number of arguments it can accept, so greet('Alice') and greet('Alice', 'Hi') both reach the same definition.

Point(int x = 0, int y = 0) { this.x = x; this.y = y }

Default values also work for constructors. new Point() uses (0, 0), and new Point(5) sets only x.

Default values are evaluated on each call, so an expression such as List items = [] creates a new list every time the method runs. A default cannot be skipped by position: to pass a value for punct, you must also pass a value for greeting, for example greet('Carol', 'Hello', '?'). Named arguments are not available for ordinary methods; they work with a Map parameter or with @NamedVariant, as shown above.

$ groovy Defaults.groovy
Hello, Alice!
Hi, Bob!
Hey, Carol?
(0, 0)
(5, 0)
(5, 7)

Best Practices

Source

Groovy Object Orientation Documentation

This tutorial covered Groovy interfaces with abstract and default methods, inheritance with super calls, named parameters through maps and @NamedVariant, and default parameter values for methods and constructors.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than eight years of experience in teaching programming.

List all Groovy tutorials.