Groovy functional interfaces and SAM type coercion with 12 examples. Covers closure conversion, Java interop, and custom SAM types. Tested on Groovy 5.x.
“The best interface is the one you don’t have to think about – Groovy’s SAM coercion makes Java’s functional interfaces disappear into closures.”
Brian Goetz, Java Concurrency in Practice
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Intermediate to Advanced | Reading Time: 20 minutes
If you’ve been writing Groovy alongside Java, you’ve probably run into a situation where a Java method expects a Comparator, a Runnable, a Predicate, or some other interface with a single method. In Java 8+, you’d use a lambda. In Groovy, you just pass a closure – and Groovy automatically converts it for you.
This magic is called SAM type coercion. SAM stands for “Single Abstract Method,” and it refers to any interface (or abstract class) that has exactly one abstract method. Groovy can automatically convert a closure into an implementation of any SAM type, making Java interop incredibly smooth.
If you’ve followed our Groovy Closures Complete Guide, you already know closures are first-class citizens in Groovy. SAM coercion takes this further – it lets your closures stand in for any single-method interface, bridging Groovy’s dynamic world with Java’s type-safe functional programming.
In this post, we’ll cover everything about Groovy functional interfaces and SAM types with 12 tested examples. You’ll learn how SAM coercion works, when it kicks in automatically, how to define your own SAM types, and the patterns that make Groovy-Java interop smooth.
Table of Contents
What Are Functional Interfaces and SAM Types?
A functional interface is a Java term (introduced in Java 8) for an interface with exactly one abstract method. Java marks them with the @FunctionalInterface annotation, though the annotation is optional – any interface with one abstract method qualifies.
A SAM type is Groovy’s broader term for the same concept. According to the official Groovy documentation, Groovy’s SAM coercion works with:
- Interfaces with exactly one abstract method (like Java’s
Runnable,Comparator,Predicate) - Abstract classes with exactly one abstract method (Groovy extends the concept beyond interfaces)
- Interfaces with default methods are SAM types as long as only one method is abstract
Common Java SAM types you’ll encounter:
Common Java Functional Interfaces
// java.lang Runnable → void run() Callable<V> → V call() // java.util Comparator<T> → int compare(T o1, T o2) // java.util.function (Java 8+) Function<T, R> → R apply(T t) Predicate<T> → boolean test(T t) Consumer<T> → void accept(T t) Supplier<T> → T get() BiFunction<T, U, R> → R apply(T t, U u) UnaryOperator<T> → T apply(T t) BinaryOperator<T> → T apply(T t1, T t2)
In Groovy, you don’t need to think about most of these. You write a closure, and Groovy figures out which SAM type to convert it to based on the context.
How Groovy’s SAM Coercion Works
Groovy performs SAM coercion in two ways:
1. Implicit coercion – When a method parameter expects a SAM type, Groovy automatically wraps your closure:
Implicit SAM Coercion
// Java's Collections.sort expects Comparator - Groovy converts the closure
def names = ['Charlie', 'Alice', 'Bob']
Collections.sort(names, { a, b -> a <=> b })
println names // [Alice, Bob, Charlie]
2. Explicit coercion with as – You can explicitly cast a closure to a SAM type:
Explicit SAM Coercion
// Explicit cast with 'as'
def comparator = { a, b -> a <=> b } as Comparator
println comparator.getClass().interfaces.contains(Comparator) // true
The key rule: Groovy matches the closure’s parameter count to the SAM method’s parameter count. If your closure has the wrong number of parameters, you’ll get a runtime error. Groovy’s dynamic typing handles the type matching – your closure parameters don’t need explicit types.
Closures vs Functional Interfaces
It’s worth understanding the relationship between Groovy closures and Java functional interfaces, because they’re related but different:
- Groovy Closure: An object of type
groovy.lang.Closure– has delegate, owner, thisObject, supports currying, composition, and trampoline - Java Lambda: A lightweight implementation of a functional interface – no overhead of a full Closure object
- SAM-coerced Closure: A Groovy closure wrapped to implement a specific interface – retains closure features internally
When Groovy coerces a closure to a SAM type, it creates a proxy that implements the interface and delegates to the closure. The closure itself still exists inside – you can access its features if you cast back. But from the Java side, it looks like a regular implementation of the interface.
This is different from Java lambdas, which are compiled to invokedynamic bytecode and don’t carry the overhead of a Closure object. For most practical purposes, the difference doesn’t matter – but it’s good to know when profiling performance-critical code.
12 Practical SAM Type Examples
Let’s work through functional interfaces and SAM types from basic to advanced, building on concepts from our closures guide.
Example 1: Closure as Runnable
What we’re doing: Using a Groovy closure where Java expects a Runnable.
Example 1: Closure as Runnable
// Runnable has one method: void run()
// Groovy auto-converts a no-arg closure
// Implicit coercion - pass closure where Runnable is expected
def thread1 = new Thread({ println "Thread 1: Hello from closure-as-Runnable!" })
thread1.start()
thread1.join()
// Explicit coercion with 'as'
Runnable task = { println "Task: I'm a Runnable now!" }
println "Type: ${task.getClass().simpleName}"
println "Is Runnable: ${task instanceof Runnable}"
task.run()
// Using with Timer (another Runnable consumer)
def results = []
def runnable = {
results << "Executed at ${System.currentTimeMillis()}"
} as Runnable
// Run it directly
runnable.run()
println "Result: ${results[0]}"
// Variable assignment with type declaration
Runnable greet = { println "Hello from typed variable!" }
greet.run()
Output
Thread 1: Hello from closure-as-Runnable! Type: script_from_string$_run_closure2 Is Runnable: true Task: I'm a Runnable now! Result: Executed at 1741408800000 Hello from typed variable!
What happened here: Runnable is the simplest functional interface – it has one method (run()) that takes no arguments and returns nothing. Groovy happily converts a no-arg closure into a Runnable. Notice the implicit coercion with new Thread({ ... }) – you don’t need as Runnable because the Thread constructor declares the parameter type. The type declaration Runnable greet = { ... } triggers coercion at assignment time.
Example 2: Closure as Comparator
What we’re doing: Using closures as Comparator instances for sorting.
Example 2: Closure as Comparator
// Comparator has: int compare(T o1, T o2)
// Groovy converts a two-arg closure
// Implicit - sort() accepts Comparator
def names = ['Charlie', 'alice', 'Bob', 'dave']
def sorted = names.sort(false, { a, b -> a.compareToIgnoreCase(b) })
println "Sorted: ${sorted}"
// Explicit coercion
Comparator<String> byLength = { a, b -> a.length() <=> b.length() }
def byLen = names.sort(false, byLength)
println "By length: ${byLen}"
// Using with Collections.sort (Java API)
def numbers = [42, 7, 15, 3, 99, 28]
Collections.sort(numbers, { a, b -> b <=> a }) // Descending
println "Descending: ${numbers}"
// Using with TreeSet (ordered collection)
def treeSet = new TreeSet<String>({ a, b -> a.compareToIgnoreCase(b) } as Comparator)
treeSet.addAll(['banana', 'Apple', 'cherry', 'AVOCADO'])
println "TreeSet: ${treeSet}"
// Comparator with reversed()
Comparator<Integer> ascending = { a, b -> a <=> b }
def descending = ascending.reversed()
def nums = [5, 2, 8, 1, 9]
println "Ascending: ${nums.sort(false, ascending)}"
println "Descending: ${nums.sort(false, descending)}"
Output
Sorted: [alice, Bob, Charlie, dave] By length: [Bob, dave, alice, Charlie] Descending: [99, 42, 28, 15, 7, 3] TreeSet: [Apple, AVOCADO, banana, cherry] Ascending: [1, 2, 5, 8, 9] Descending: [9, 8, 5, 2, 1]
What happened here: Comparator is probably the SAM type you’ll use most often. Groovy converts any two-argument closure into a Comparator automatically. Notice the TreeSet example – the constructor expects a Comparator, and Groovy handles the conversion smoothly. When you declare Comparator<Integer> ascending = { ... }, the coerced closure gains access to all Comparator interface methods, including reversed().
Example 3: Closures as java.util.function Types
What we’re doing: Using Groovy closures with Java 8’s functional interfaces from java.util.function.
Example 3: java.util.function Types
import java.util.function.*
// Function<T, R> - takes T, returns R
Function<String, Integer> strlen = { it.length() }
println "Length of 'Groovy': ${strlen.apply('Groovy')}"
// Predicate<T> - takes T, returns boolean
Predicate<Integer> isEven = { it % 2 == 0 }
println "4 is even: ${isEven.test(4)}"
println "7 is even: ${isEven.test(7)}"
// Consumer<T> - takes T, returns void
Consumer<String> printer = { println " Consumer says: ${it}" }
printer.accept("Hello!")
// Supplier<T> - takes nothing, returns T
Supplier<String> greeter = { "Hello, World!" }
println "Supplier: ${greeter.get()}"
// BiFunction<T, U, R> - takes two args, returns R
BiFunction<String, Integer, String> repeat = { str, times ->
str * times
}
println "Repeat: ${repeat.apply('ha', 3)}"
// UnaryOperator<T> - takes T, returns T
UnaryOperator<String> shouty = { it.toUpperCase() + "!" }
println "Shouty: ${shouty.apply('groovy')}"
// Chaining with andThen (Java's compose)
Function<String, String> trim = { it.trim() }
Function<String, String> upper = { it.toUpperCase() }
Function<String, String> pipeline = trim.andThen(upper)
println "Pipeline: ${pipeline.apply(' hello groovy ')}"
Output
Length of 'Groovy': 6 4 is even: true 7 is even: false Consumer says: Hello! Supplier: Hello, World! Repeat: hahaha Shouty: GROOVY! Pipeline: HELLO GROOVY
What happened here: Every java.util.function type works with Groovy closures. You declare a variable with the functional interface type, assign a closure, and Groovy coerces it automatically. The coerced closure then exposes the interface’s methods – apply(), test(), accept(), get() – plus composition methods like andThen(). This gives you the best of both worlds: Groovy’s concise closure syntax with Java’s composable functional interface API.
Example 4: Closures with Java Streams
What we’re doing: Using Groovy closures directly with Java’s Stream API, which is built entirely on functional interfaces.
Example 4: Closures with Java Streams
import java.util.stream.*
// Stream.filter expects Predicate
// Stream.map expects Function
// Stream.forEach expects Consumer
def names = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve']
// Using closures with stream operations
def result = names.stream()
.filter({ it.length() > 3 }) // Predicate<String>
.map({ it.toUpperCase() }) // Function<String, String>
.sorted({ a, b -> b <=> a }) // Comparator<String>
.collect(Collectors.toList())
println "Stream result: ${result}"
// Numeric stream with closures
def sum = (1..10).stream()
.filter({ it % 2 == 0 }) // Predicate<Integer>
.map({ it * it }) // Function<Integer, Integer>
.reduce(0, { acc, val -> acc + val }) // BinaryOperator<Integer>
println "Sum of even squares: ${sum}"
// flatMap with closures
def sentences = ['Hello World', 'Groovy Is Great']
def words = sentences.stream()
.flatMap({ s -> Arrays.stream(s.split(' ')) }) // Function<String, Stream<String>>
.map({ it.toLowerCase() })
.distinct()
.sorted()
.collect(Collectors.toList())
println "Words: ${words}"
// Collecting with custom collector
def grouped = names.stream()
.collect(Collectors.groupingBy({ it.length() }))
println "Grouped by length: ${grouped}"
Output
Stream result: [DIANA, CHARLIE, ALICE] Sum of even squares: 220 Words: [great, groovy, hello, is, world] Grouped by length: [3:[Bob, Eve], 5:[Alice, Diana], 7:[Charlie]]
What happened here: Java’s Stream API is built entirely on functional interfaces – Predicate for filter(), Function for map(), Consumer for forEach(). Groovy coerces closures to the right type based on what each stream method expects. You don’t need to think about which functional interface to use – just write closures with the right number of parameters and Groovy handles the rest.
Example 5: Defining Custom SAM Interfaces
What we’re doing: Creating your own functional interfaces and using them with Groovy closures.
Example 5: Custom SAM Interfaces
// Define a custom functional interface
interface Transformer {
String transform(String input)
}
// Define another with generics
interface Validator<T> {
boolean isValid(T value)
}
// Define one with a descriptive name
interface PriceCalculator {
BigDecimal calculate(BigDecimal basePrice, int quantity)
}
// Use with closure - implicit coercion via type declaration
Transformer upper = { it.toUpperCase() }
Transformer reverse = { it.reverse() }
Transformer exclaim = { it + "!" }
println "Upper: ${upper.transform('groovy')}"
println "Reverse: ${reverse.transform('groovy')}"
println "Exclaim: ${exclaim.transform('groovy')}"
// Custom validator
Validator<String> emailValidator = { it.contains('@') && it.contains('.') }
Validator<Integer> ageValidator = { it >= 0 && it <= 150 }
println "Valid email: ${emailValidator.isValid('user@test.com')}"
println "Valid email: ${emailValidator.isValid('invalid')}"
println "Valid age: ${ageValidator.isValid(25)}"
println "Valid age: ${ageValidator.isValid(-5)}"
// Price calculator
PriceCalculator standard = { price, qty -> price * qty }
PriceCalculator withDiscount = { price, qty ->
def total = price * qty
qty >= 10 ? total * 0.9 : total // 10% off for bulk
}
println "Standard: \$${standard.calculate(10.00, 5)}"
println "Bulk (5): \$${withDiscount.calculate(10.00, 5)}"
println "Bulk (10): \$${withDiscount.calculate(10.00, 10)}"
Output
Upper: GROOVY Reverse: yvoorg Exclaim: groovy! Valid email: true Valid email: false Valid age: true Valid age: false Standard: $50.00 Bulk (5): $50.00 Bulk (10): $90.000
What happened here: Custom SAM interfaces work exactly like the built-in Java ones. Define an interface with one abstract method, and Groovy will coerce closures to it. The advantage of custom interfaces over raw closures is type safety and self-documentation – PriceCalculator tells you exactly what the closure is for, while Closure is generic. The generic Validator<T> interface shows that SAM types support generics smoothly.
Example 6: SAM Coercion in Method Parameters
What we’re doing: Writing methods that accept SAM type parameters and calling them with closures.
Example 6: SAM Types in Method Parameters
import java.util.function.*
// Method accepting Predicate
def filterItems(List items, Predicate filter) {
items.findAll { filter.test(it) }
}
// Method accepting Function
def transformItems(List items, Function transformer) {
items.collect { transformer.apply(it) }
}
// Method accepting multiple SAM types
def processData(List data, Predicate filter, Function mapper, Consumer action) {
data.findAll { filter.test(it) }
.collect { mapper.apply(it) }
.each { action.accept(it) }
}
// Call with closures - automatic coercion
def numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def evens = filterItems(numbers, { it % 2 == 0 })
println "Evens: ${evens}"
def doubled = transformItems(numbers, { it * 2 })
println "Doubled: ${doubled}"
// All three closures coerced to different types
println "Processing:"
processData(
[1, 2, 3, 4, 5, 6],
{ it > 3 }, // Predicate
{ it * 10 }, // Function
{ println " Value: ${it}" } // Consumer
)
// Using custom interface in method signature
interface StringMapper {
String map(String input)
}
def applyMapping(List<String> items, StringMapper mapper) {
items.collect { mapper.map(it) }
}
println "Mapped: ${applyMapping(['hello', 'world'], { it.capitalize() })}"
Output
Evens: [2, 4, 6, 8, 10] Doubled: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] Processing: Value: 40 Value: 50 Value: 60 Mapped: [Hello, World]
What happened here: When you declare method parameters as functional interface types, Groovy automatically coerces any closure passed to those parameters. The processData method takes three different SAM types, and each closure is coerced independently based on the parameter’s declared type. This pattern makes your APIs type-safe while keeping call sites clean and closure-based.
Example 7: SAM Interfaces with Default Methods
What we’re doing: Working with interfaces that have both a SAM method and default methods – the closure only needs to implement the abstract one.
Example 7: SAM with Default Methods
// Interface with one abstract method and default methods
interface TextProcessor {
// The one abstract method (SAM)
String process(String input)
// Default methods - come for free
default String processAndTrim(String input) {
process(input).trim()
}
default String processUpper(String input) {
process(input).toUpperCase()
}
default List<String> processAll(List<String> inputs) {
inputs.collect { process(it) }
}
}
// Only need to implement process() - defaults are inherited
TextProcessor addBrackets = { "[${it}]" }
println "Process: ${addBrackets.process('hello')}"
println "Process & trim: ${addBrackets.processAndTrim(' hello ')}"
println "Process upper: ${addBrackets.processUpper('hello')}"
println "Process all: ${addBrackets.processAll(['a', 'b', 'c'])}"
// Another implementation
TextProcessor reverser = { it.reverse() }
println "\nReverse all: ${reverser.processAll(['hello', 'world'])}"
println "Reverse upper: ${reverser.processUpper('groovy')}"
// Interface with static factory and default methods
interface Formatter {
String format(Object value)
default String formatWithLabel(String label, Object value) {
"${label}: ${format(value)}"
}
}
Formatter currencyFmt = { "\$${String.format('%.2f', it as double)}" }
Formatter percentFmt = { "${String.format('%.1f', it as double)}%" }
println "\n${currencyFmt.format(42.5)}"
println currencyFmt.formatWithLabel("Price", 42.5)
println percentFmt.formatWithLabel("Tax Rate", 8.5)
Output
Process: [hello] Process & trim: [ hello ] Process upper: [HELLO] Process all: [[a], [b], [c]] Reverse all: [olleh, dlrow] Reverse upper: YVOORG Price: $42.50 Tax Rate: 8.5%
What happened here: An interface with default methods is still a SAM type as long as it has exactly one abstract method. The closure only implements the abstract method – all default methods are inherited automatically. This is a powerful design pattern: define a rich interface with multiple default methods, and users only need to provide a single closure for the core behavior. The rest comes for free through the defaults.
Example 8: The ‘as’ Keyword for Explicit Coercion
What we’re doing: Using explicit as coercion to convert closures to specific interface types.
Example 8: Explicit ‘as’ Coercion
// When implicit coercion isn't enough (ambiguous context)
def taskClosure = { println " Running task..." }
// Explicit coercion
def runnable = taskClosure as Runnable
def callable = { "Task result" } as java.util.concurrent.Callable
println "Runnable type: ${runnable.getClass().interfaces[0].simpleName}"
println "Callable result: ${callable.call()}"
// Coercion to custom interfaces
interface Logger {
void log(String message)
}
interface Renderer {
String render(Object data)
}
def logClosure = { msg -> println " LOG: ${msg}" }
def logger = logClosure as Logger
logger.log("Hello from SAM logger!")
// Same closure, different coercion
def closure = { a, b -> a + b }
def comparator = closure as Comparator
def biFunction = closure as java.util.function.BiFunction
println "As Comparator: ${comparator.compare(3, 5)}"
println "As BiFunction: ${biFunction.apply(3, 5)}"
// Coercion in a list/map context
def renderers = [
html: { "<b>${it}</b>".toString() } as Renderer,
text: { it.toString() } as Renderer,
json: { "\"${it}\"".toString() } as Renderer
]
renderers.each { name, renderer ->
println "${name}: ${renderer.render('hello')}"
}
Output
Runnable type: GeneratedClosure Callable result: Task result LOG: Hello from SAM logger! As Comparator: 8 As BiFunction: 8 html: hello text: hello json: "hello"
What happened here: The as keyword forces Groovy to coerce a closure to a specific SAM type. This is necessary when the context is ambiguous – for example, when storing coerced closures in a map where Groovy can’t infer the target type from a method signature. Notice how the same closure { a, b -> a + b } can be coerced to both Comparator and BiFunction – the closure doesn’t change, only its type wrapper does.
Example 9: SAM Coercion with Event Listeners
What we’re doing: Using closures where Java event listener interfaces are expected.
Example 9: Event Listener SAM Coercion
// Simulating Java-style event system
interface EventListener {
void onEvent(String eventName, Map data)
}
interface EventFilter {
boolean shouldProcess(String eventName)
}
class EventSystem {
List<EventListener> listeners = []
EventFilter filter = { true } as EventFilter // Default: accept all
void setFilter(EventFilter f) { this.filter = f }
void addListener(EventListener listener) { listeners << listener }
void fireEvent(String name, Map data = [:]) {
if (filter.shouldProcess(name)) {
listeners.each { it.onEvent(name, data) }
} else {
println " Event '${name}' filtered out"
}
}
}
def system = new EventSystem()
// Add listeners using closures
system.addListener({ name, data ->
println " Logger: ${name} → ${data}"
} as EventListener)
system.addListener({ name, data ->
if (name.startsWith('error')) {
println " Alert: ERROR detected! ${data.message ?: 'No details'}"
}
} as EventListener)
// Set filter using closure
system.setFilter({ !it.startsWith('debug') } as EventFilter)
// Fire events
println "=== Firing events ==="
system.fireEvent('user.login', [user: 'Alice'])
system.fireEvent('error.db', [message: 'Connection timeout'])
system.fireEvent('debug.trace', [detail: 'Step 1']) // Filtered out
system.fireEvent('order.placed', [id: 'ORD-001'])
Output
=== Firing events === Logger: user.login → [user:Alice] Logger: error.db → [message:Connection timeout] Alert: ERROR detected! Connection timeout Event 'debug.trace' filtered out Logger: order.placed → [id:ORD-001]
What happened here: Event-driven Java APIs are full of single-method listener interfaces. In Java, you’d create anonymous inner classes or use lambdas. In Groovy, you pass closures with as coercion. Each closure is wrapped to implement the correct listener interface while keeping the code concise. The filter closure demonstrates that SAM coercion works for predicates too – the EventFilter interface has one boolean method, so any single-argument boolean closure can serve as a filter.
Example 10: SAM Coercion with Abstract Classes
What we’re doing: Using closures with abstract classes that have a single abstract method – a Groovy extension beyond Java’s functional interfaces.
Example 10: Abstract Class SAM Coercion
// Abstract class with one abstract method + concrete methods
abstract class DataConverter {
// The single abstract method
abstract Object convert(Object input)
// Concrete helper methods
List convertAll(List inputs) {
inputs.collect { convert(it) }
}
Object convertOrDefault(Object input, Object defaultValue) {
try {
convert(input)
} catch (Exception e) {
defaultValue
}
}
String convertToString(Object input) {
convert(input).toString()
}
}
// Closure coerced to abstract class!
DataConverter toInt = { Integer.parseInt(it.toString().trim()) } as DataConverter
println "Convert '42': ${toInt.convert('42')}"
println "Convert all: ${toInt.convertAll(['1', '2', '3', '4', '5'])}"
println "Convert or default: ${toInt.convertOrDefault('bad', -1)}"
println "Convert to string: ${toInt.convertToString('99')}"
// Another converter
DataConverter toUpper = { it.toString().toUpperCase() } as DataConverter
println "\nUpper all: ${toUpper.convertAll(['hello', 'world'])}"
println "Upper or default: ${toUpper.convertOrDefault(null, 'N/A')}"
// Abstract class with constructor parameter
abstract class NamedProcessor {
String name
abstract String process(String input)
NamedProcessor() { this.name = "anonymous" }
NamedProcessor(String name) { this.name = name }
String processWithLabel() { "${name}: ready" }
}
// Note: closure SAM coercion uses the no-arg constructor
NamedProcessor proc = { it.reverse() } as NamedProcessor
println "\nProcessor name: ${proc.name}"
println "Process: ${proc.process('groovy')}"
Output
Convert '42': 42 Convert all: [1, 2, 3, 4, 5] Convert or default: -1 Convert to string: 99 Upper all: [HELLO, WORLD] Upper or default: N/A Processor name: anonymous Process: yvoorg
What happened here: This is a Groovy-specific feature – Java’s lambdas can only implement interfaces, not abstract classes. Groovy’s SAM coercion works with abstract classes too, as long as they have exactly one abstract method. The concrete methods (like convertAll and convertOrDefault) are inherited automatically. When coercing to an abstract class, Groovy uses the no-arg constructor if available. This pattern lets you provide a rich base class with utilities, and users only implement the core logic via a closure.
Example 11: Multi-Interface Implementation with Map Coercion
What we’re doing: Implementing interfaces with multiple methods using Groovy’s map coercion – going beyond single-method SAM types.
Example 11: Map Coercion for Multi-Method Interfaces
// Multi-method interface (NOT a SAM type)
interface Repository {
Object findById(int id)
List findAll()
void save(Object item)
void delete(int id)
}
// Use map coercion - each key maps to a method
def storage = [:]
def repo = [
findById: { id -> storage[id] },
findAll: { -> storage.values() as List },
save: { item -> storage[item.id] = item; println " Saved: ${item}" },
delete: { id -> storage.remove(id); println " Deleted: ${id}" }
] as Repository
// Use the "repository"
repo.save([id: 1, name: 'Groovy Guide'])
repo.save([id: 2, name: 'Java Patterns'])
repo.save([id: 3, name: 'Kotlin Cookbook'])
println "Find #2: ${repo.findById(2)}"
println "All: ${repo.findAll()}"
repo.delete(1)
println "After delete: ${repo.findAll()}"
// Combining SAM and map coercion
interface Cacheable {
Object get(String key)
void put(String key, Object value)
boolean contains(String key)
}
def cache = [:]
def cacheImpl = [
get: { key -> cache[key] },
put: { key, value -> cache[key] = value },
contains: { key -> cache.containsKey(key) }
] as Cacheable
cacheImpl.put("lang", "Groovy")
println "\nCache contains 'lang': ${cacheImpl.contains('lang')}"
println "Cache get 'lang': ${cacheImpl.get('lang')}"
println "Cache contains 'other': ${cacheImpl.contains('other')}"
Output
Saved: [id:1, name:Groovy Guide] Saved: [id:2, name:Java Patterns] Saved: [id:3, name:Kotlin Cookbook] Find #2: [id:2, name:Java Patterns] All: [[id:1, name:Groovy Guide], [id:2, name:Java Patterns], [id:3, name:Kotlin Cookbook]] Deleted: 1 After delete: [[id:2, name:Java Patterns], [id:3, name:Kotlin Cookbook]] Cache contains 'lang': true Cache get 'lang': Groovy Cache contains 'other': false
What happened here: When an interface has more than one abstract method, it’s not a SAM type – you can’t use a single closure. But Groovy offers map coercion as an alternative: you create a map where each key matches a method name and each value is a closure implementing that method. The as Interface cast creates a proxy that dispatches method calls to the appropriate closure in the map. This is Groovy’s lightweight alternative to anonymous inner classes for multi-method interfaces.
Example 12: Real-World Pattern – Strategy with SAM Types
What we’re doing: Implementing the strategy pattern using SAM interfaces, closures, and dynamic selection.
Example 12: Strategy Pattern with SAM Types
// Define strategy interfaces
interface PricingStrategy {
BigDecimal calculatePrice(BigDecimal basePrice, int quantity)
}
interface DiscountStrategy {
BigDecimal applyDiscount(BigDecimal total, String customerTier)
}
interface TaxStrategy {
BigDecimal calculateTax(BigDecimal amount, String region)
}
// Strategy registry
class OrderCalculator {
PricingStrategy pricing
DiscountStrategy discount
TaxStrategy tax
Map calculate(BigDecimal basePrice, int qty, String tier, String region) {
def subtotal = pricing.calculatePrice(basePrice, qty)
def afterDiscount = discount.applyDiscount(subtotal, tier)
def taxAmount = tax.calculateTax(afterDiscount, region)
def total = afterDiscount + taxAmount
[
subtotal: subtotal,
discount: subtotal - afterDiscount,
tax: taxAmount,
total: total
]
}
}
// Create strategies using closures
def standardPricing = { price, qty -> price * qty } as PricingStrategy
def bulkPricing = { price, qty ->
qty >= 100 ? price * qty * 0.85 :
qty >= 50 ? price * qty * 0.90 :
qty >= 10 ? price * qty * 0.95 :
price * qty
} as PricingStrategy
def tierDiscount = { total, tier ->
switch (tier) {
case 'gold': return total * 0.85
case 'silver': return total * 0.90
case 'bronze': return total * 0.95
default: return total
}
} as DiscountStrategy
def usTax = { amount, region ->
def rates = [CA: 0.0725, NY: 0.08, TX: 0.0625, OR: 0.0]
amount * (rates[region] ?: 0.05)
} as TaxStrategy
// Assemble calculator with different strategies
def calculator = new OrderCalculator(
pricing: bulkPricing,
discount: tierDiscount,
tax: usTax
)
// Calculate orders
def scenarios = [
[price: 25.00, qty: 5, tier: 'bronze', region: 'CA'],
[price: 25.00, qty: 50, tier: 'gold', region: 'NY'],
[price: 25.00, qty: 100, tier: 'silver', region: 'OR']
]
scenarios.each { s ->
def result = calculator.calculate(s.price, s.qty, s.tier, s.region)
println "Order: ${s.qty} x \$${s.price} (${s.tier}/${s.region})"
println " Subtotal: \$${String.format('%.2f', result.subtotal)}"
println " Discount: -\$${String.format('%.2f', result.discount)}"
println " Tax: +\$${String.format('%.2f', result.tax)}"
println " Total: \$${String.format('%.2f', result.total)}"
println()
}
Output
Order: 5 x $25.00 (bronze/CA) Subtotal: $125.00 Discount: -$6.25 Tax: +$8.61 Total: $127.36 Order: 50 x $25.00 (gold/NY) Subtotal: $1125.00 Discount: -$168.75 Tax: +$76.50 Total: $1032.75 Order: 100 x $25.00 (silver/OR) Subtotal: $2125.00 Discount: -$212.50 Tax: +$95.63 Total: $2008.13
What happened here: The strategy pattern is a natural fit for SAM types. Each strategy interface has one method, so each can be implemented with a closure. The OrderCalculator class doesn’t know or care whether the strategies are closures, anonymous classes, or full implementations – it just calls the interface methods. Swapping strategies is as easy as passing a different closure. In production, you might register strategies in a map and select them by configuration, making the entire pricing engine configurable without code changes.
SAM Coercion with Abstract Classes
We touched on abstract class SAM coercion in Example 10, but it’s worth highlighting the rules and edge cases:
Abstract Class SAM Rules
// Rule 1: Must have exactly ONE abstract method
abstract class SingleAbstract {
abstract String transform(String s)
String twice(String s) { transform(s) + transform(s) }
}
SingleAbstract upper = { it.toUpperCase() } as SingleAbstract
println "Transform: ${upper.transform('hi')}"
println "Twice: ${upper.twice('hi')}"
// Rule 2: No-arg constructor must exist (or be the only constructor)
abstract class WithNoArg {
String name = "default"
abstract String process(String input)
}
WithNoArg proc = { "Processed: ${it}".toString() } as WithNoArg
println "Name: ${proc.name}"
println "Process: ${proc.process('hello')}"
// Rule 3: Multiple abstract methods = NOT a SAM type
abstract class TwoAbstract {
abstract String methodA(String s)
abstract String methodB(String s)
}
// This would fail: TwoAbstract impl = { ... } as TwoAbstract
// Use map coercion instead:
def impl = [
methodA: { "A: ${it}".toString() },
methodB: { "B: ${it}".toString() }
] as TwoAbstract
println "Method A: ${impl.methodA('test')}"
println "Method B: ${impl.methodB('test')}"
Output
Transform: HI Twice: HIHI Name: default Process: Processed: hello Method A: A: test Method B: B: test
The ability to coerce closures to abstract classes is a Groovy-only feature. Java’s lambdas are strictly limited to interfaces. This makes Groovy more flexible when you want to provide a rich base class with utility methods while keeping the implementation surface to a single method.
Best Practices
DO:
- Let implicit SAM coercion work when the target type is clear from method signatures
- Use
asfor explicit coercion when storing SAM implementations in untyped collections - Define custom SAM interfaces with descriptive names –
PricingStrategyis more readable thanBiFunction<BigDecimal, Integer, BigDecimal> - Add default methods to your SAM interfaces to provide utility methods for free
- Use abstract class SAM coercion when you need a rich base class with helper methods
DON’T:
- Force
ascoercion when implicit coercion already works – it adds noise - Try to coerce a closure to a multi-method interface – use map coercion instead
- Forget to match the closure parameter count to the SAM method’s parameter count
- Use SAM coercion in performance-critical inner loops – the proxy has overhead compared to direct method calls
- Mix up Groovy’s
Closuretype with Java’s functional interface types when defining APIs – pick one and be consistent
Conclusion
We’ve covered everything about Groovy functional interfaces and SAM types – from basic Runnable and Comparator coercion to Java Stream integration, custom SAM interfaces with default methods, abstract class coercion, map coercion for multi-method interfaces, and the strategy pattern. SAM coercion is the bridge that makes Groovy closures work well with Java’s entire functional programming ecosystem.
The big takeaway: wherever Java expects a single-method interface, Groovy lets you pass a closure. No anonymous inner classes, no verbose lambda syntax – just a closure with the right number of parameters. Combined with closures, this makes Groovy one of the smoothest languages for Java interop. This applies to Java Streams, Spring callbacks, and your own strategy interfaces – SAM coercion keeps your code clean and expressive.
Summary
- SAM (Single Abstract Method) types include interfaces and abstract classes with exactly one abstract method
- Groovy automatically coerces closures to SAM types when the target type is known from method signatures
- Use
asfor explicit coercion when the context is ambiguous - Groovy extends SAM coercion to abstract classes – something Java lambdas cannot do
- Map coercion (
[method: closure] as Interface) handles multi-method interfaces that aren’t SAM types
If you also work with build tools, CI/CD pipelines, or cloud CLIs, check out Command Playground to practice 105+ CLI tools directly in your browser — no install needed.
Up next: Groovy Streams vs Closures
Frequently Asked Questions
What is a SAM type in Groovy?
SAM stands for Single Abstract Method. A SAM type is any interface or abstract class that has exactly one abstract method. Groovy can automatically convert a closure into an implementation of a SAM type, making it easy to use closures wherever Java expects functional interfaces like Runnable, Comparator, Predicate, or Function.
What is the difference between a Groovy closure and a Java lambda?
A Groovy closure is an object of type groovy.lang.Closure with features like delegate, owner, currying, and composition. A Java lambda is a lightweight implementation of a functional interface compiled to invokedynamic bytecode. When Groovy coerces a closure to a SAM type, it wraps the closure in a proxy – the closure features still exist internally, but the external API matches the interface.
Can Groovy closures implement abstract classes, not just interfaces?
Yes. Unlike Java lambdas (which only work with interfaces), Groovy’s SAM coercion extends to abstract classes with exactly one abstract method. The abstract class must have a no-arg constructor. Concrete methods and fields in the abstract class are inherited by the coerced closure wrapper. Use ‘as AbstractClassName’ for explicit coercion.
How do I implement a multi-method interface with closures in Groovy?
Use map coercion. Create a map where each key is a method name and each value is a closure implementing that method, then cast with ‘as InterfaceName’. For example: def impl = [methodA: { … }, methodB: { … }] as MyInterface. This works for interfaces with any number of methods, not just SAM types.
When should I use ‘as’ for explicit SAM coercion versus implicit coercion?
Use implicit coercion (just pass the closure) when the target type is clear from a method parameter declaration – Groovy figures it out automatically. Use explicit ‘as’ coercion when storing in untyped variables, adding to collections, or when the context is ambiguous. If you declare the variable type (e.g., Runnable r = { … }), the type declaration triggers implicit coercion without needing ‘as’.
Related Posts
Previous in Series: Groovy Closure Composition and Chaining
Next in Series: Groovy Streams vs Closures
Related Topics You Might Like:
- Groovy Closures – The Complete Guide
- Groovy Method References and Method Pointers
- Groovy Closure Composition and Chaining
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment