Traits

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Apache Groovy 6.0 (6.0.0, 2026-09-22) and every example was run with Groovy 6.0.0 on JDK 26 (checked on 2026-10-14). A trait is a reusable set of methods and state that a class composes in with implements. It fills the gap between interfaces (which carry no implementation) and abstract classes (of which a class can extend only one): a class can implement many traits and gets their methods, properties and fields. Traits arrived in Groovy 2.3. Static members on traits remain incubating in Groovy 6.0. Interfaces and inheritance are on Inheritance, Interfaces & Generics; restricting which classes may implement a trait with sealed is on Records & Sealed Types.

Mechanically, the compiler turns a trait into an interface plus a helper class holding the method bodies, and copies the trait’s behaviour and state into each implementing class when that class is compiled. A trait is therefore a real type: instanceof works, but a trait cannot be instantiated.

trait Greets { String greet() { 'hi' } }
println Greets.isInterface()
try { new Greets() } catch (e) { println e.class.simpleName }
true
GroovyRuntimeException

Methods

A trait method is public by default. A trait can declare abstract methods the implementing class must supply, concrete methods with a body, and private methods that are visible only inside the trait.

trait Describable {
    String label = 'unnamed'                  // property
    private int calls = 0                     // private field
    abstract String id()                      // abstract method
    String describe() { count(); "${label} (${id()})" }
    final String kind() { 'describable' }     // final method
    private void count() { calls = calls + 1 } // private method
    int calls() { calls }
}
class Pot implements Describable {
    String id() { 'pot-7' }
}
def p = new Pot(label: 'basil pot')
println p.describe()
println p.describe()
println p.calls()
println p.kind()
println Pot.interfaces*.simpleName
basil pot (pot-7)
basil pot (pot-7)
2
describable
[Describable, FieldHelper, GroovyObject]

FieldHelper is a compiler-generated interface that carries the trait’s fields; it is an implementation detail. A class that does not implement the abstract id() is rejected at compile time like any class that misses an interface method.

A private trait method is callable only from the trait’s own code, not from the implementing class or from outside:

trait Hidden {
    private String note() { 'private' }
    String reveal() { note() }
}
class User implements Hidden {}
println new User().reveal()
try { new User().note() } catch (e) { println e.class.simpleName }
private
MissingMethodException

A final modifier on a trait method only sets the modifier of the method woven into the implementing class; it does not make the method impossible to redefine. Running trait Fin { final String kind() { 'fixed' } } with class Over implements Fin { String kind() { 'changed' } } printed changed in Groovy 6.0.0. To get a method that cannot be overridden, put it in a base class that implements the trait.

The meaning of this

Inside a trait, this is the instance of the implementing class, not an instance of the trait. Calling this.class returns the class that implements the trait, and returning this from a trait method returns the implementing object.

trait Greeter {
    String greet() { "Hello from ${this.class.simpleName}" }
    Greeter self() { this }
}
class Gardener implements Greeter {}
def g = new Gardener()
println g.greet()
assert g.self().is(g)
println g.self().class.simpleName
Hello from Gardener
Gardener

Interfaces

A trait can implement interfaces (it then provides, or leaves abstract, their methods), and a class implementing the trait becomes an instance of those interfaces too.

interface Named { String name() }
trait Hello implements Named {
    String hello() { "Hello, ${name()}" }
}
class Bot implements Hello { String name() { 'bot' } }
println new Bot().hello()
println(new Bot() instanceof Named)
Hello, bot
true

Properties

A property declared in a trait (with no visibility modifier, like a class property) is turned into a getter and setter in the implementing class, backed by a field that lives in that class. Each implementing class therefore gets its own copy of the value, and named-argument construction works.

trait Meter {
    String unit = 'C'
    String show(double v) { "${v} ${unit}" }
}
class Probe implements Meter {}
class Probe2 implements Meter {}
def p = new Probe(unit: 'F')
println p.show(70.5)
println new Probe2().show(1)
70.5 F
1.0 C

Fields

A trait may also declare plain fields, private or public. They are stored in the implementing class. The compiler renames them with the trait’s name as prefix, so two traits that declare the same field name do not collide and a class’s own field of the same name stays separate. For the same reason a field is not reachable as bean.field from outside; reach it through a method.

trait Tagged {
    String tag = 'none'                 // property: getter/setter generated in the class
    private int visits = 0              // field: stored in the implementing class
    public String zone = 'z1'           // public field: name is mangled in the class
    void visit() { visits = visits + 1 }
    int visitCount() { visits }
    String zoneName() { zone }
}
class Bed implements Tagged {}
def b = new Bed()
b.tag = 'north'
b.visit(); b.visit()
println b.tag
println b.visitCount()
println b.zoneName()
println Bed.declaredFields*.name.sort()
north
2
z1
[$staticClassInfo, Tagged__tag, Tagged__visits, Tagged__zone, __$stMC, metaClass]

Note the mangled names Tagged__tag and so on. Inside a trait, the increment and decrement operators (visits++, ++visits) on fields and properties are rejected at compile time (Postfix expressions on trait fields/properties are not supported in traits), which is why the example uses visits = visits + 1.

Composition of behaviours

A class implements several traits and receives the methods of all of them. When names do not clash, composition is just the union of the methods. Where a method exists in more than one place the precedence is: a method defined in the class itself wins over a trait method, and a trait method wins over a method inherited from a superclass.

trait Waters { String act() { 'watering' } }
trait Lights { String act2() { 'lighting' } }
class Greenhouse implements Waters, Lights {
    String all() { "${act()} and ${act2()}" }
}
println new Greenhouse().all()
class Base { String act() { 'base' } }
class Child extends Base implements Waters {}
println new Child().act()      // trait wins over inherited superclass method
class Own implements Waters { String act() { 'own' } }
println new Own().act()        // class method wins over trait
watering and lighting
watering
own

The diagram summarises how traits compose into a class and how a name clash is resolved. Greenhouse receives both traits' methods; AB implements two traits that both define who(), so the later declaration (B) wins; Explicit defines who() itself and chooses with Trait.super.method().

classDiagram class Waters { <<trait>> +act() String } class Lights { <<trait>> +act2() String } class A { <<trait>> +who() String } class B { <<trait>> +who() String } class Greenhouse { +all() String } class AB { +who() String } class Explicit { +who() String } Waters <|.. Greenhouse : composes Lights <|.. Greenhouse : composes A <|.. AB : implements A, B B <|.. AB : last declared wins A <|.. Explicit : A.super.who() B <|.. Explicit : B.super.who()

Overriding default methods

A class overrides a trait method by defining a method with the same signature; it can still reach the trait’s version with TraitName.super.method().

trait Greets { String greet() { 'hello' } }
class Plain implements Greets {
    String greet() { 'class: ' + Greets.super.greet() }
}
println new Plain().greet()
class: hello

Extending traits

A trait can extend another trait with extends, or implement several with implements (a trait implementing traits gets their methods); a trait method calls the parent trait’s version with super.

trait Greets { String greet() { 'hello' } }
trait Loud extends Greets {
    String greet() { super.greet().toUpperCase() + '!' }
}
class Shouter implements Loud {}
println new Shouter().greet()

trait Moves { String go() { 'moves' } }
trait Rolls extends Moves { String go() { 'rolls, ' + super.go() } }
class Rover implements Rolls {}
println new Rover().go()
println Rover.interfaces*.simpleName
HELLO!
rolls, moves
[Rolls, GroovyObject]

Duck typing and traits

A trait is a type, so a trait implementer is an instance of it, but other objects that just happen to have the same method work through ordinary dynamic dispatch without being instances.

trait Quacks { String sound() { 'quack' } }
class Mallard implements Quacks {}
class Toy { String sound() { 'squeak' } }
def d = new Mallard()
println([d, new Toy()]*.sound())
println(d instanceof Quacks)
println(new Toy() instanceof Quacks)
[quack, squeak]
true
false

Dynamic methods in a trait

A trait can implement MOP hooks such as methodMissing, propertyMissing and setProperty; implementing classes inherit that behaviour for names they do not define themselves (see Runtime Metaprogramming & the MOP).

trait DynamicObject {
    private Map props = [:]
    def methodMissing(String name, args) { name.toUpperCase() }
    def propertyMissing(String name) { props.get(name) }
    void setProperty(String name, Object value) { props.put(name, value) }
}
class Dynamic implements DynamicObject {
    String existingProperty = 'ok'
    String existingMethod() { 'ok' }
}
def d = new Dynamic()
println d.existingProperty
println d.foo
d.foo = 'bar'
println d.foo
println d.existingMethod()
println d.someMethod()
ok
null
bar
ok
SOMEMETHOD

Multiple inheritance conflicts

If two implemented traits define the same method signature, the default resolution is that the trait declared last in the implements list wins. Reversing the order reverses the winner.

trait A { String who() { 'A' } }
trait B { String who() { 'B' } }
class AB implements A, B {}
class BA implements B, A {}
println new AB().who()           // last declared wins
println new BA().who()
B
A

Conflict resolution

To choose explicitly, define the method in the class and call the wanted version, or both, with Trait.super.method().

trait A { String who() { 'A' } }
trait B { String who() { 'B' } }
class Explicit implements A, B {
    String who() { A.super.who() + '+' + B.super.who() }
}
println new Explicit().who()
A+B

Runtime implementation of traits

A trait can be applied to a single object at runtime, without changing its class. as applies one trait; withTraits(...) applies several. The result is a new object that implements the traits and the interfaces of the original object, but it is not an instance of the original class. Conflicts follow the same last-wins rule.

trait Walks { String move() { 'walks' } }
trait Swims { String move() { 'swims' } }
class Frog { String name = 'frog' }
def f = new Frog().withTraits(Walks, Swims)
println f.move()
println f.name
println(f instanceof Walks)
println(f instanceof Swims)
def g = new Frog() as Walks
println g.move()
println(g instanceof Frog)
swims
frog
true
true
walks
false

Neither as nor withTraits keeps the original class: g instanceof Frog is false, and so is the same test on a withTraits result. The original object is left unchanged:

trait Writes { String write() { 'writes' } }
class Foo {}
def foo = new Foo()
try { foo.write() } catch (e) { println e.class.simpleName }
def t = foo.withTraits(Writes)
println t.write()
println foo.class == t.class
MissingMethodException
writes
false

Chaining behaviour

When several traits override the same method and each one calls super, they form a chain, in the same way as stackable modifications in Scala. The last trait applied runs first; each super call moves to the previous trait, and the implementing class’s own method sits at the end of the chain. The traits must implement a common interface so that super has a target.

interface Handler { String handle(String msg) }
trait Trimming implements Handler {
    String handle(String msg) { super.handle(msg.trim()) }
}
trait Upper implements Handler {
    String handle(String msg) { super.handle(msg.toUpperCase()) }
}
trait Tagging implements Handler {
    String handle(String msg) { super.handle('<' + msg + '>') }
}
class Terminal implements Handler {
    String handle(String msg) { "[${msg}]" }
}
println new Terminal().withTraits(Trimming, Upper, Tagging).handle('  hi ')
println new Terminal().withTraits(Tagging, Upper, Trimming).handle('  hi ')
[<  HI >]
[<HI>]

With order Trimming, Upper, Tagging, Tagging runs first (wrapping the text in < and >), then Upper, then Trimming (which trims the already wrapped text, so the spaces stay inside the angle brackets), and finally Terminal.handle. Reversing the order makes Trimming run first and the spaces disappear before the brackets are added.

An unqualified super in a trait delegates to the next trait in the chain, and when no trait is left it refers to the superclass of the implementing object. That makes it possible to decorate a final JDK class at runtime:

trait Filtering {
    StringBuilder append(String str) { super.append(str.replace('o', '')) }
    String toString() { super.toString() }
}
def sb = new StringBuilder().withTraits Filtering
sb.append('Groovy')
println sb.toString()
println(sb instanceof StringBuilder)
Grvy
false

SAM coercion

A trait with a single abstract method can be implemented by a closure, as for an interface: assigning the closure to a variable of the trait type, or using as. The closure becomes the abstract method and the trait’s concrete methods are available.

trait Callback {
    abstract String call(String x)
    String twice(String x) { call(call(x)) }
}
Callback cb = { String s -> s + '!' }
println cb.twice('go')
def cb2 = { String s -> s.reverse() } as Callback
println cb2.twice('abc')
println cb2.call('xy')
go!!
abc
yx

Differences with Java 8 default methods

A Java 8 interface default method is used only when no class in the hierarchy supplies the method. A trait method is different: when the class declares the trait in its implements list and does not define the method itself, the trait version is used even if a superclass defines it. The composition example above shows this (Child prints watering, not base), and it lets a trait override behaviour that a superclass already implements, such as a shared test setup().

Differences with mixins

Groovy’s older mixin mechanism (metaClass.mixin, @Mixin) copies methods at runtime and does not make the target an instance of the mixed-in type. Traits compose at compile time (or at runtime through withTraits), the implementing class is a trait instance, and they work with @CompileStatic and @TypeChecked. Prefer traits for new code.

trait Greets { String greet() { 'hi' } }
class Plain { String greet() { 'plain' } }
class Target {}
class WithTrait implements Greets {}
println(new WithTrait() instanceof Greets)       // trait: real type
Target.metaClass.mixin(Plain)                     // mixin: runtime-only, no type relation
println new Target().greet()
println(new Target() instanceof Plain)
true
plain
false

metaClass.mixin is described on Metaclasses, ExpandoMetaClass & Categories.

Static members (incubating in 6.0)

A trait may declare static methods, properties and fields. Each implementing class receives its own copy of the static state, so counters in two classes are independent. The area is still incubating in Groovy 6.0, and the full specification is GEP-22. Groovy 6.0 clarified and enhanced it:

  • a public trait static method is declarer-bound by default: a call from trait code reaches the trait’s own copy, and the method is promoted onto the generated interface as a JVM static, so both Trait.m() and Impl.m() resolve;

  • a static method annotated @groovy.transform.Virtual can be overridden by an implementing class, and trait code that calls it dispatches through the implementing class (a template-method pattern for statics);

  • @Virtual is valid only on public, non-abstract static trait methods; and the forms T.this.* and an unqualified super.m(...) from a static trait method are rejected at compile time.

trait Counter {
    static int created = 0
    static int next() { created = created + 1 }
    static String kind() { 'generic' }
}
class A implements Counter {}
class B implements Counter {}
A.next(); A.next(); B.next()
println "A=${A.created} B=${B.created}"
println A.kind()
A=2 B=1
generic
import groovy.transform.Virtual
trait Validateable {
    @Virtual static boolean defaultNullable() { false }
    static boolean check() { defaultNullable() }
}
class Book implements Validateable {
    static boolean defaultNullable() { true }
}
class Pen implements Validateable {}
println Book.check()
println Pen.check()
true
false

Inheritance of state gotchas

A trait’s fields live in the implementing class, which creates a few surprises:

  • each class, and each runtime-created object, gets its own state; there is no state shared through the trait (except static members as above, which are per implementing class);

  • if the implementing class declares a property with the same name as a trait property, the class’s own field and accessors win for outside callers, but the trait code keeps reading its own field, so the two disagree;

  • because trait fields are renamed (Tagged__visits), they cannot be read as bean.field from outside.

trait Config {
    String name
    String describe() { "name=${name}" }
}
class Svc implements Config {
    String name = 'svc-class'   // class property with the same name
}
println new Svc().describe()
println new Svc().name
name=null
svc-class

Avoid redeclaring a trait property in the implementing class; set it through the constructor or setter instead.

Self types

A trait can require that its implementers are of a given class (or implement given types) by annotating it with @groovy.transform.SelfType. The trait code can then use that type’s members, and the compiler rejects an implementer that does not satisfy the self type.

import groovy.transform.SelfType
class Device { String serial = 'sn-1' }
@SelfType(Device)
trait Registers {
    String register() { "registered ${serial}" }
}
class Meter extends Device implements Registers {}
println new Meter().register()
registered sn-1

Making a class that does not extend Device implement Registers fails to compile:

class 'NotDevice' implements trait 'Registers' but does not extend self type class 'Device'

Without @SelfType, a trait written for a specific class compiles and runs only because member lookup is dynamic, and it fails under @CompileStatic because the trait does not know it will be a Device; @SelfType fixes both.

Limitations

  • Traits are not officially compatible with AST transformations. Some, like @CompileStatic, are applied to the trait itself rather than to implementing classes, others to both, and there is no guarantee that a transformation behaves on a trait as it does on a class.

  • Prefix and postfix operations that update a field of the trait are rejected; use x = x + 1 or +=.

  • A trait cannot be instantiated, and final on a trait method does not prevent redefinition (shown earlier).

  • Private trait methods are visible only inside the trait.

  • Static members are incubating in 6.0 and have the dispatch rules listed in that section.

Where the book is outdated

Books written before Groovy 2.3 do not cover traits at all, and cover @Mixin and metaClass.mixin as the way to compose behaviour; @Mixin was deprecated and is superseded by traits. Static trait members and @Virtual were refined in Groovy 6.0.