Groovy Traits — Reusable Behavior Composition
In this tutorial, you will learn about Groovy Traits. We cover key concepts, practical examples, and best practices to help you master this topic.
Groovy traits are reusable behavior units that provide method implementations and state, supporting multiple inheritance of behavior without the diamond problem through linearization.
What You'll Learn
- Defining and implementing traits
- Trait inheritance and composition
- Trait state and fields
- Trait with interfaces
Why It Matters
Traits are the modern replacement for mixins, providing type-checkable, compilable behavior composition. Doda Browser uses traits to compose HTTP middleware behavior across different handler implementations.
Real-World Use
Framework extension points, reusable logging/metrics/auditing behavior, plugin systems, and cross-cutting concerns that need to be type-safe.
flowchart LR
A["Trait"] --> B["Methods"]
A --> C["Fields"]
A --> D["Interfaces"]
B --> E["Default Impl"]
C --> F["State"]
D --> G["Contracts"]
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
Defining a Trait
trait Logging {
void log(String msg) {
println "[${new Date()}] $msg"
}
void error(String msg) {
println "[ERROR] ${new Date()} $msg"
}
}
class Service implements Logging {
def process() {
log "Processing started"
error "Something failed"
}
}
new Service().process()
Trait with Fields
trait Counter {
int count = 0
void increment() { count++ }
void decrement() { count-- }
int current() { count }
}
class ClickTracker implements Counter {}
def ct = new ClickTracker()
ct.increment()
ct.increment()
println ct.current() // 2
println ct.count // 2
Fields in traits are added to the implementing class automatically.
Multiple Traits
trait Logging {
void log(String msg) { println "LOG: $msg" }
}
trait Metrics {
void metric(String name, Object value) {
println "METRIC: $name = $value"
}
}
trait Validation {
boolean validateNotNull(Object obj) {
obj != null
}
}
class Application implements Logging, Metrics, Validation {
def run() {
log "Starting"
metric("users", 100)
println validateNotNull("test") // true
}
}
Trait Method Resolution
When multiple traits define the same method, linearization determines which is used:
trait A {
void greet() { println "A" }
}
trait B {
void greet() { println "B" }
}
// The rightmost trait wins
class MyClass implements A, B {}
new MyClass().greet() // B
class MyClass2 implements B, A {}
new MyClass2().greet() // A
Using super in Traits
Traits support super for stackable behavior:
trait Logging {
void process() {
log "Processing"
}
void log(String msg) { println "LOG: $msg" }
}
trait Timing {
void process() {
def start = System.currentTimeMillis()
super.process()
def elapsed = System.currentTimeMillis() - start
println "Took ${elapsed}ms"
}
}
class CoreProcessor {
void process() {
println "Core processing"
}
}
class TimedLoggedProcessor extends CoreProcessor implements Logging, Timing {}
new TimedLoggedProcessor().process()
// Core processing
// Took Xms
// LOG: Processing (from Logging.process)
Trait Inheritance
trait Animal {
abstract String sound()
void speak() { println sound() }
}
trait Dog implements Animal {
String sound() { "Woof" }
}
trait Cat implements Animal {
String sound() { "Meow" }
}
class Puppy implements Dog {}
class Kitten implements Cat {}
new Puppy().speak() // Woof
new Kitten().speak() // Meow
Trait with @CompileStatic
Unlike mixins, traits work with static compilation:
import groovy.transform.CompileStatic
trait Calculator {
int add(int a, int b) { a + b }
int multiply(int a, int b) { a * b }
}
@CompileStatic
class StaticService implements Calculator {
int compute(int x) {
add(x, multiply(x, 2))
}
}
def svc = new StaticService()
println svc.compute(5) // 15
Common Mistakes
1. Using fields in traits with @CompileStatic
Trait fields are dynamically added. @CompileStatic on the interface may not see them.
2. Trait method conflicts
When two traits define the same method, linearization applies. Explicitly override to resolve.
3. Traits cannot be instantiated directly
trait T {}
// new T() — ERROR: traits cannot be instantiated
4. Private fields in traits
Private fields are accessible only within the trait, not from the implementing class.
5. Traits and constructors
Traits cannot have constructors with parameters. Use a Factory method or afterPropertiesSet pattern.
Practice Questions
1. What is the difference between a trait and an abstract class?
Traits support multiple inheritance (multiple traits per class). Abstract classes support single inheritance. Traits cannot have parameterized constructors.
2. How does trait linearization work?
Methods in the rightmost trait take precedence. implements A, B, C means C wins.
3. Can traits have state?
Yes, traits can declare fields. The implementing class gets those fields.
4. Why are traits preferred over @Mixin?
Traits are compile-time, type-checkable, work with @CompileStatic, and support super calls.
Challenge: Create a trait hierarchy that models animals with different locomotion (walking, flying, swimming).
FAQ
{{< faq question="Can traits be used from Java?" >}} Yes, Groovy traits compile to Java interfaces with default methods. Java code can implement them. {{< /faq >}}
{{< faq question="Do traits support runtime type checking?" >}}
Yes, instanceof works with traits: obj instanceof Logging returns true if the class implements the trait.
{{< /faq >}}
{{< faq question="Can a trait extend a class?" >}} No, traits cannot extend classes. They can only implement other traits. {{< /faq >}}
{{< faq question="What is the performance overhead of traits?" >}} With @CompileStatic, zero overhead. Dynamic dispatch adds minimal cost similar to interface calls. {{< /faq >}}
{{< faq question="Are traits unique to Groovy?" >}} No, PHP, Scala, and Rust have similar concepts. Traits were inspired by Scala's trait system. {{< /faq >}}
Mini Project
Create a middleware pipeline using traits that can Process HTTP requests with logging, authentication, and Caching.
trait Logging {
void before(String msg) { println "LOG: $msg" }
}
trait Authentication {
boolean authenticate(String token) { token == "valid-token" }
}
trait Caching {
private cache = [:]
Object getFromCache(String key) { cache[key] }
void addToCache(String key, Object value) { cache[key] = value }
}
class RequestHandler implements Logging, Authentication, Caching {
String handle(String token, String request) {
before("Request: $request")
if (!authenticate(token)) return "401 Unauthorized"
def cached = getFromCache(request)
if (cached) return "Cached: $cached"
def result = "Processed: $request"
addToCache(request, result)
result
}
}
def handler = new RequestHandler()
println handler.handle("valid-token", "get-users")
// LOG: Request: get-users
// Processed: get-users
What's Next
Now that you understand traits, proceed to command chains in Groovy.
| Topic | Description | Link |
|---|---|---|
| Command chains | Fluent DSL syntax | {{< ref "23-command-chains" >}} |
| Mixins | Runtime injection | {{< ref "20-mixins" >}} |
| Operator overloading | Custom operators | {{< ref "24-operator-overloading" >}} |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro