Get started with Groovy enums through 12 tested examples. Learn enum constants, methods, constructors, interfaces, and best practices. Complete guide for Groovy 5.x.
“If you’re using magic strings or integer constants to represent a fixed set of values, you’re writing bugs that haven’t happened yet. Enums exist to make impossible states impossible.”
Joshua Bloch, Effective Java
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 18 minutes
Using strings like "ACTIVE", "INACTIVE", "PENDING" for status values works until someone types "Active" or "ACTVE" and you spend an hour tracking down the bug. A groovy enum solves this with a fixed set of type-safe constants that the compiler verifies – and Groovy adds enhancements beyond what Java enums offer.
This post is your complete guide to Groovy enums. We’ll cover 12 tested examples showing basic enums, enums with properties and methods, constructors, interfaces, enum iteration, and all the Groovy-specific enhancements that make enums more powerful than in Java. If you need to convert strings to enums, we have a dedicated String to Enum guide. And for understanding Groovy’s dynamic typing alongside enums, check out our def keyword guide.
Every example is tested on Groovy 5.x with exact output shown. Let’s get into it.
Table of Contents
What is an Enum in Groovy?
An enum (short for enumeration) is a special class that represents a fixed set of constants. When you declare an enum, you’re defining all possible values at compile time. The compiler ensures that no other values can exist – you literally cannot create a new instance of an enum outside of its declaration.
Groovy enums are built on top of Java enums (they compile to java.lang.Enum) but add several Groovy-specific features that make them more flexible. According to the official Groovy documentation, Groovy enums support the full range of Java enum capabilities plus Groovy’s own enhancements like closures, GDK methods on enum collections, and integration with Groovy’s switch statement.
Key Points:
- Enums define a fixed, closed set of values – no new instances can be created
- Every enum constant is an instance of the enum class
- Enums can have properties, methods, constructors, and implement interfaces
- Enum constants are implicitly
public static final - Groovy enums work well with
switchstatements, collections, and the GDK - Built-in methods:
values(),valueOf(),name(),ordinal()
Enum Syntax and Basics
Enum Syntax Overview
// Simplest enum declaration
enum Direction {
NORTH, SOUTH, EAST, WEST
}
// Using an enum value
def heading = Direction.NORTH
println "Heading: ${heading}"
println "Type: ${heading.getClass().name}"
println "Name: ${heading.name()}"
println "Ordinal: ${heading.ordinal()}"
println ""
// All enum values
println "All directions: ${Direction.values() as List}"
// Enum valueOf
def east = Direction.valueOf("EAST")
println "valueOf('EAST'): ${east}"
// Enum comparison
assert Direction.NORTH == Direction.NORTH
assert Direction.NORTH != Direction.SOUTH
println "Comparisons: passed"
Output
Heading: NORTH
Type: Direction
Name: NORTH
Ordinal: 0
All directions: [NORTH, SOUTH, EAST, WEST]
valueOf('EAST'): EAST
Comparisons: passed
The name() method returns the string name of the constant, and ordinal() returns its zero-based position in the declaration order. The values() method returns an array of all constants, and valueOf(String) converts a string to the corresponding enum constant (it’s case-sensitive and throws IllegalArgumentException if no match is found).
12 Practical Enum Examples
Example 1: Basic Enum with switch Statement
What we’re doing: Using enums with Groovy’s switch statement for clean, type-safe branching.
Example 1: Enum with switch
enum Season {
SPRING, SUMMER, AUTUMN, WINTER
}
def describeWeather(Season season) {
switch (season) {
case Season.SPRING:
return "Mild and blooming"
case Season.SUMMER:
return "Hot and sunny"
case Season.AUTUMN:
return "Cool and colorful"
case Season.WINTER:
return "Cold and frosty"
}
}
Season.values().each { season ->
println "${season.name().padRight(8)} -> ${describeWeather(season)}"
}
println ""
// Groovy's switch also works with enum ranges
def isWarm(Season s) {
switch (s) {
case [Season.SPRING, Season.SUMMER]:
return true
default:
return false
}
}
Season.values().each { s ->
println "${s}: warm = ${isWarm(s)}"
}
Output
SPRING -> Mild and blooming SUMMER -> Hot and sunny AUTUMN -> Cool and colorful WINTER -> Cold and frosty SPRING: warm = true SUMMER: warm = true AUTUMN: warm = false WINTER: warm = false
What happened here: Enums and switch are natural partners. Each case handles one enum constant, and the compiler can help you identify missing cases. Notice that in Groovy’s switch, you can use a list [Season.SPRING, Season.SUMMER] as a case – Groovy checks if the value is in the list. This is more concise than having separate cases with fall-through.
Example 2: Enum with Properties and Constructor
What we’re doing: Adding properties and a constructor to an enum so each constant carries data.
Example 2: Enum with Properties
enum Planet {
MERCURY(3.303e+23, 2.4397e6),
VENUS(4.869e+24, 6.0518e6),
EARTH(5.976e+24, 6.37814e6),
MARS(6.421e+23, 3.3972e6),
JUPITER(1.9e+27, 7.1492e7),
SATURN(5.688e+26, 6.0268e7),
URANUS(8.686e+25, 2.5559e7),
NEPTUNE(1.024e+26, 2.4746e7)
final double mass // in kilograms
final double radius // in meters
// Enum constructor must be private (or package-private)
Planet(double mass, double radius) {
this.mass = mass
this.radius = radius
}
// Gravitational constant
static final double G = 6.67300E-11
double surfaceGravity() {
return G * mass / (radius * radius)
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity()
}
}
def earthWeight = 75.0 // kg
def mass = earthWeight / Planet.EARTH.surfaceGravity()
println "Your weight on different planets (Earth weight: ${earthWeight} kg):"
Planet.values().each { planet ->
def weight = String.format("%.2f", planet.surfaceWeight(mass))
println " ${planet.name().padRight(8)} : ${weight} kg"
}
Output
Your weight on different planets (Earth weight: 75.0 kg): MERCURY : 28.33 kg VENUS : 67.87 kg EARTH : 75.00 kg MARS : 28.41 kg JUPITER : 189.79 kg SATURN : 79.95 kg URANUS : 67.88 kg NEPTUNE : 85.37 kg
What happened here: This is the classic Planet enum example from the Java documentation, adapted for Groovy. Each enum constant passes values to the constructor, giving every planet its own mass and radius. Methods like surfaceGravity() can use those properties. This pattern is incredibly useful – your enum constants aren’t just labels, they carry meaningful data and behavior.
Example 3: Enum with Methods
What we’re doing: Adding custom methods to enums for business logic that belongs with the constants.
Example 3: Enum with Methods
enum HttpStatus {
OK(200, "OK"),
CREATED(201, "Created"),
BAD_REQUEST(400, "Bad Request"),
UNAUTHORIZED(401, "Unauthorized"),
FORBIDDEN(403, "Forbidden"),
NOT_FOUND(404, "Not Found"),
INTERNAL_ERROR(500, "Internal Server Error"),
SERVICE_UNAVAILABLE(503, "Service Unavailable")
final int code
final String message
HttpStatus(int code, String message) {
this.code = code
this.message = message
}
boolean isSuccess() { code >= 200 && code < 300 }
boolean isClientError() { code >= 400 && code < 500 }
boolean isServerError() { code >= 500 }
String getCategory() {
if (isSuccess()) return "Success"
if (isClientError()) return "Client Error"
if (isServerError()) return "Server Error"
return "Unknown"
}
// Static lookup by code
static HttpStatus fromCode(int code) {
values().find { it.code == code }
}
@Override
String toString() { "${code} ${message}" }
}
// Use the enum
println "All statuses:"
HttpStatus.values().each { status ->
println " ${status} [${status.category}]"
}
println ""
// Lookup by code
def status = HttpStatus.fromCode(404)
println "Code 404: ${status}"
println "Is client error: ${status.isClientError()}"
println "Is success: ${status.isSuccess()}"
println ""
// Filter by category
def errors = HttpStatus.values().findAll { it.isClientError() || it.isServerError() }
println "Error statuses: ${errors.collect { it.code }}"
Output
All statuses: 200 OK [Success] 201 Created [Success] 400 Bad Request [Client Error] 401 Unauthorized [Client Error] 403 Forbidden [Client Error] 404 Not Found [Client Error] 500 Internal Server Error [Server Error] 503 Service Unavailable [Server Error] Code 404: 404 Not Found Is client error: true Is success: false Error statuses: [400, 401, 403, 404, 500, 503]
What happened here: This enum packs a lot of functionality. Each constant has a numeric code and message. Methods like isSuccess() and isClientError() provide categorization logic. The static fromCode() method allows reverse lookup from an integer. The overridden toString() gives a readable representation. Notice how Groovy’s findAll works directly on the enum values array.
Example 4: Enum Implementing an Interface
What we’re doing: Making enum constants implement an interface, with each constant providing its own implementation.
Example 4: Enum with Interface
interface Calculable {
double calculate(double a, double b)
String getSymbol()
}
enum Operation implements Calculable {
ADD('+'),
SUBTRACT('-'),
MULTIPLY('*'),
DIVIDE('/')
final String symbol
Operation(String symbol) {
this.symbol = symbol
}
String getSymbol() { symbol }
double calculate(double a, double b) {
switch (this) {
case ADD: return a + b
case SUBTRACT: return a - b
case MULTIPLY: return a * b
case DIVIDE:
if (b == 0) throw new ArithmeticException("Division by zero")
return a / b
}
}
}
// Use the operations
def a = 10.0
def b = 3.0
Operation.values().each { op ->
try {
def result = String.format("%.2f", op.calculate(a, b))
println "${a} ${op.symbol} ${b} = ${result}"
} catch (Exception e) {
println "${a} ${op.symbol} ${b} = ERROR: ${e.message}"
}
}
println ""
// Operations as first-class objects
def operations = [Operation.ADD, Operation.MULTIPLY]
def results = operations.collect { it.calculate(5, 3) }
println "ADD and MULTIPLY of 5,3: ${results}"
// Use in a calculator
def evaluate(String expression) {
def matcher = expression =~ /(\d+\.?\d*)\s*([+\-*\/])\s*(\d+\.?\d*)/
if (matcher) {
def (_, numA, symbol, numB) = matcher[0]
def op = Operation.values().find { it.symbol == symbol }
assert op : "Unknown operator: ${symbol}"
return op.calculate(numA as double, numB as double)
}
throw new IllegalArgumentException("Cannot parse: ${expression}")
}
println "10 + 5 = ${evaluate('10 + 5')}"
println "20 * 3 = ${evaluate('20 * 3')}"
println "15 / 4 = ${evaluate('15 / 4')}"
Output
10.0 + 3.0 = 13.00 10.0 - 3.0 = 7.00 10.0 * 3.0 = 30.00 10.0 / 3.0 = 3.33 ADD and MULTIPLY of 5,3: [8.0, 15.0] 10 + 5 = 15.0 20 * 3 = 60.0 15 / 4 = 3.75
What happened here: Each enum constant provides its own implementation of the Calculable interface. This is known as “constant-specific method implementation” – each constant is essentially an anonymous subclass of the enum. This pattern replaces if-else chains or switch statements with polymorphism. The calculator example shows how enums can be looked up and used as strategy objects.
Example 5: Enum Iteration and Collection Operations
What we’re doing: Using Groovy’s collection methods on enums for filtering, grouping, and transforming.
Example 5: Enum Collections
enum Priority {
CRITICAL(1, "red"),
HIGH(2, "orange"),
MEDIUM(3, "yellow"),
LOW(4, "green"),
TRIVIAL(5, "gray")
final int level
final String color
Priority(int level, String color) {
this.level = level
this.color = color
}
boolean isUrgent() { level <= 2 }
}
// Iterate all values
println "All priorities:"
Priority.values().each { p ->
println " ${p.name().padRight(10)} level=${p.level} color=${p.color} urgent=${p.isUrgent()}"
}
println ""
// Filter
def urgent = Priority.values().findAll { it.isUrgent() }
println "Urgent: ${urgent*.name()}"
// Find
def medium = Priority.values().find { it.level == 3 }
println "Level 3: ${medium}"
// Collect/Transform
def colorMap = Priority.values().collectEntries { [it.name(), it.color] }
println "Color map: ${colorMap}"
// Sort (by a custom property)
def byLevel = Priority.values().sort { it.level }
println "By level: ${byLevel*.name()}"
def reversed = Priority.values().sort { -it.level }
println "Reversed: ${reversed*.name()}"
// Group by
def grouped = Priority.values().groupBy { it.isUrgent() ? "Urgent" : "Normal" }
grouped.each { category, priorities ->
println "${category}: ${priorities*.name()}"
}
// Count
println "Urgent count: ${Priority.values().count { it.isUrgent() }}"
println "Total count: ${Priority.values().length}"
Output
All priorities: CRITICAL level=1 color=red urgent=true HIGH level=2 color=orange urgent=true MEDIUM level=3 color=yellow urgent=false LOW level=4 color=green urgent=false TRIVIAL level=5 color=gray urgent=false Urgent: [CRITICAL, HIGH] Level 3: MEDIUM Color map: [CRITICAL:red, HIGH:orange, MEDIUM:yellow, LOW:green, TRIVIAL:gray] By level: [CRITICAL, HIGH, MEDIUM, LOW, TRIVIAL] Reversed: [TRIVIAL, LOW, MEDIUM, HIGH, CRITICAL] Urgent: [CRITICAL, HIGH] Normal: [MEDIUM, LOW, TRIVIAL] Urgent count: 2 Total count: 5
What happened here: Groovy’s GDK methods work on enum arrays just like they work on lists. The spread operator *. extracts a property from every element – urgent*.name() calls name() on each enum constant. collectEntries turns the enum into a map, groupBy organizes them into categories, and count tallies matches. This is where Groovy enums really shine compared to Java.
Example 6: Enum with Abstract Methods
What we’re doing: Defining abstract methods on an enum that each constant must implement, creating a strategy pattern.
Example 6: Abstract Enum Methods
enum TextFormatter {
UPPERCASE {
String format(String text) { text.toUpperCase() }
String describe() { "Converts all characters to uppercase" }
},
LOWERCASE {
String format(String text) { text.toLowerCase() }
String describe() { "Converts all characters to lowercase" }
},
TITLE_CASE {
String format(String text) {
text.split(/\s+/).collect { it.capitalize() }.join(" ")
}
String describe() { "Capitalizes the first letter of each word" }
},
CAMEL_CASE {
String format(String text) {
def words = text.split(/\s+/)
words[0].toLowerCase() + words[1..-1].collect { it.capitalize() }.join("")
}
String describe() { "Converts to camelCase format" }
},
SNAKE_CASE {
String format(String text) {
text.toLowerCase().replaceAll(/\s+/, '_')
}
String describe() { "Converts to snake_case format" }
}
abstract String format(String text)
abstract String describe()
}
def input = "hello groovy world"
println "Input: '${input}'"
println ""
TextFormatter.values().each { formatter ->
println "${formatter.name().padRight(12)} : '${formatter.format(input)}'"
println " ${formatter.describe()}"
}
println ""
// Use as a function parameter
def applyFormatters(String text, TextFormatter... formatters) {
formatters.each { f ->
println "${f.name()}: ${f.format(text)}"
}
}
applyFormatters("my variable name", TextFormatter.CAMEL_CASE, TextFormatter.SNAKE_CASE)
Output
Input: 'hello groovy world'
UPPERCASE : 'HELLO GROOVY WORLD'
Converts all characters to uppercase
LOWERCASE : 'hello groovy world'
Converts all characters to lowercase
TITLE_CASE : 'Hello Groovy World'
Capitalizes the first letter of each word
CAMEL_CASE : 'helloGroovyWorld'
Converts to camelCase format
SNAKE_CASE : 'hello_groovy_world'
Converts to snake_case format
CAMEL_CASE: myVariableName
SNAKE_CASE: my_variable_name
What happened here: When you declare abstract methods in an enum, every constant must provide an implementation. This is the strategy pattern without needing separate classes – each enum constant encapsulates its own formatting logic. The applyFormatters function demonstrates using enums as strategy objects that can be passed around.
Example 7: Enum with Closures (Groovy-Specific)
What we’re doing: Using closures as enum properties – a Groovy-specific pattern that’s not possible in Java.
Example 7: Enum with Closures
enum Validator {
NOT_EMPTY("Must not be empty", { String s -> s?.trim() }),
MIN_LENGTH_3("Must be at least 3 characters", { String s -> s?.length() >= 3 }),
MAX_LENGTH_50("Must be at most 50 characters", { String s -> s?.length() <= 50 }),
ALPHA_ONLY("Must contain only letters", { String s -> s ==~ /[a-zA-Z]+/ }),
ALPHANUMERIC("Must contain only letters and digits", { String s -> s ==~ /[a-zA-Z0-9]+/ }),
EMAIL("Must be a valid email", { String s -> s ==~ /\S+@\S+\.\S+/ })
final String errorMessage
final Closure<Boolean> rule
Validator(String errorMessage, Closure<Boolean> rule) {
this.errorMessage = errorMessage
this.rule = rule
}
boolean validate(String input) {
return rule(input)
}
}
// Validate a single value against specific validators
def validateField(String fieldName, String value, Validator... validators) {
def errors = []
validators.each { v ->
if (!v.validate(value)) {
errors << v.errorMessage
}
}
if (errors) {
println "${fieldName}: INVALID"
errors.each { println " - ${it}" }
} else {
println "${fieldName}: VALID"
}
return errors.isEmpty()
}
validateField("username", "Alice123", Validator.NOT_EMPTY, Validator.MIN_LENGTH_3, Validator.ALPHANUMERIC)
validateField("username", "ab", Validator.NOT_EMPTY, Validator.MIN_LENGTH_3, Validator.ALPHANUMERIC)
validateField("email", "user@example.com", Validator.NOT_EMPTY, Validator.EMAIL)
validateField("email", "not-an-email", Validator.NOT_EMPTY, Validator.EMAIL)
println ""
// Compose validators
def strictValidators = [Validator.NOT_EMPTY, Validator.MIN_LENGTH_3, Validator.MAX_LENGTH_50, Validator.ALPHA_ONLY]
def inputs = ["Hello", "", "ab", "Valid123", "ValidName"]
inputs.each { input ->
def valid = strictValidators.every { it.validate(input) }
println "'${input}'.padRight(12) -> ${valid ? 'PASS' : 'FAIL'}"
}
Output
username: VALID username: INVALID - Must be at least 3 characters email: VALID email: INVALID - Must be a valid email 'Hello'.padRight(12) -> PASS ''.padRight(12) -> FAIL 'ab'.padRight(12) -> FAIL 'Valid123'.padRight(12) -> FAIL 'ValidName'.padRight(12) -> PASS
What happened here: This is a pattern that’s unique to Groovy – you can’t store closures as properties in Java enums. Each Validator constant has a human-readable error message and a closure that performs the actual validation. This makes validators composable – you pick which validators to apply for each field. The closure-based approach is more concise than abstract methods when the logic is simple.
Example 8: Enum State Machine
What we’re doing: Building a state machine with enums where each state knows its valid transitions.
Example 8: Enum State Machine
enum OrderStatus {
PENDING,
CONFIRMED,
PROCESSING,
SHIPPED,
DELIVERED,
CANCELLED,
REFUNDED
// Define valid transitions
private static final Map<OrderStatus, List<OrderStatus>> TRANSITIONS = [
(PENDING) : [CONFIRMED, CANCELLED],
(CONFIRMED) : [PROCESSING, CANCELLED],
(PROCESSING): [SHIPPED, CANCELLED],
(SHIPPED) : [DELIVERED],
(DELIVERED) : [REFUNDED],
(CANCELLED) : [],
(REFUNDED) : []
]
List<OrderStatus> getAllowedTransitions() {
TRANSITIONS[this] ?: []
}
boolean canTransitionTo(OrderStatus target) {
target in allowedTransitions
}
OrderStatus transitionTo(OrderStatus target) {
assert canTransitionTo(target) : "Invalid transition: ${this} -> ${target}. Allowed: ${allowedTransitions}"
return target
}
boolean isTerminal() {
allowedTransitions.isEmpty()
}
}
// Show all transitions
println "State machine transitions:"
OrderStatus.values().each { status ->
def next = status.allowedTransitions ?: ["(terminal)"]
println " ${status.name().padRight(12)} -> ${next}"
}
println ""
// Simulate order lifecycle
def order = OrderStatus.PENDING
println "Order lifecycle:"
println " Current: ${order}"
order = order.transitionTo(OrderStatus.CONFIRMED)
println " -> ${order}"
order = order.transitionTo(OrderStatus.PROCESSING)
println " -> ${order}"
order = order.transitionTo(OrderStatus.SHIPPED)
println " -> ${order}"
order = order.transitionTo(OrderStatus.DELIVERED)
println " -> ${order}"
println " Terminal: ${order.isTerminal()}"
println ""
// Test invalid transition
try {
OrderStatus.SHIPPED.transitionTo(OrderStatus.PENDING)
} catch (AssertionError e) {
println "Caught invalid transition: ${e.message.split('\\.')[0]}"
}
Output
State machine transitions: PENDING -> [CONFIRMED, CANCELLED] CONFIRMED -> [PROCESSING, CANCELLED] PROCESSING -> [SHIPPED, CANCELLED] SHIPPED -> [DELIVERED] DELIVERED -> [REFUNDED] CANCELLED -> [(terminal)] REFUNDED -> [(terminal)] Order lifecycle: Current: PENDING -> CONFIRMED -> PROCESSING -> SHIPPED -> DELIVERED Terminal: true Caught invalid transition: Invalid transition: SHIPPED -> PENDING
What happened here: The state machine pattern is one of the most practical enum use cases. Each OrderStatus knows which states it can transition to. The transitionTo method uses assert to enforce valid transitions – an invalid transition is a bug. Terminal states like CANCELLED and REFUNDED have no allowed transitions. This pattern prevents invalid state changes at runtime.
Example 9: EnumSet and EnumMap
What we’re doing: Using Java’s EnumSet and EnumMap for efficient enum collections.
Example 9: EnumSet and EnumMap
enum Permission {
READ, WRITE, EXECUTE, DELETE, ADMIN
}
// EnumSet - efficient set of enum values
def userPermissions = EnumSet.of(Permission.READ, Permission.WRITE)
def adminPermissions = EnumSet.allOf(Permission)
def noPermissions = EnumSet.noneOf(Permission)
println "User: ${userPermissions}"
println "Admin: ${adminPermissions}"
println "None: ${noPermissions}"
// Add and remove
userPermissions.add(Permission.EXECUTE)
println "User + EXECUTE: ${userPermissions}"
userPermissions.remove(Permission.WRITE)
println "User - WRITE: ${userPermissions}"
// Check membership
println "Has READ: ${Permission.READ in userPermissions}"
println "Has ADMIN: ${Permission.ADMIN in userPermissions}"
println ""
// EnumSet range
def basicPerms = EnumSet.range(Permission.READ, Permission.EXECUTE)
println "Range READ..EXECUTE: ${basicPerms}"
// EnumSet complement
def notBasic = EnumSet.complementOf(basicPerms)
println "Not basic: ${notBasic}"
println ""
// EnumMap - map with enum keys (more efficient than HashMap)
def permissionDescriptions = new EnumMap<Permission, String>(Permission)
permissionDescriptions[Permission.READ] = "View files and data"
permissionDescriptions[Permission.WRITE] = "Create and modify files"
permissionDescriptions[Permission.EXECUTE] = "Run scripts and programs"
permissionDescriptions[Permission.DELETE] = "Remove files and data"
permissionDescriptions[Permission.ADMIN] = "Full system access"
println "Permission descriptions:"
permissionDescriptions.each { perm, desc ->
def hasIt = perm in userPermissions ? "[x]" : "[ ]"
println " ${hasIt} ${perm.name().padRight(8)} : ${desc}"
}
Output
User: [READ, WRITE] Admin: [READ, WRITE, EXECUTE, DELETE, ADMIN] None: [] User + EXECUTE: [READ, WRITE, EXECUTE] User - WRITE: [READ, EXECUTE] Has READ: true Has ADMIN: false Range READ..EXECUTE: [READ, WRITE, EXECUTE] Not basic: [DELETE, ADMIN] Permission descriptions: [x] READ : View files and data [ ] WRITE : Create and modify files [x] EXECUTE : Run scripts and programs [ ] DELETE : Remove files and data [ ] ADMIN : Full system access
What happened here: EnumSet and EnumMap are Java collections optimized specifically for enums. EnumSet uses a bit vector internally, making it extremely fast and memory-efficient. EnumSet.range() creates a set of all constants between two values (based on ordinal). complementOf() gives you everything not in a set. Groovy’s in operator works naturally with EnumSet for membership checks.
Example 10: Enum Serialization and JSON
What we’re doing: Converting enums to and from strings, maps, and JSON-like structures.
Example 10: Enum Serialization
enum Color {
RED("#FF0000", "Red"),
GREEN("#00FF00", "Green"),
BLUE("#0000FF", "Blue"),
YELLOW("#FFFF00", "Yellow"),
PURPLE("#800080", "Purple")
final String hex
final String displayName
Color(String hex, String displayName) {
this.hex = hex
this.displayName = displayName
}
// Convert to map (for JSON serialization)
Map toMap() {
[name: name(), hex: hex, displayName: displayName, ordinal: ordinal()]
}
// Create from map (for JSON deserialization)
static Color fromMap(Map map) {
valueOf(map.name)
}
// Lookup by hex
static Color fromHex(String hex) {
values().find { it.hex.equalsIgnoreCase(hex) }
}
}
// Serialize to map
println "Serialized:"
Color.values().each { c ->
println " ${c.toMap()}"
}
println ""
// Deserialize from map
def data = [name: "BLUE"]
def color = Color.fromMap(data)
println "From map: ${color} (${color.hex})"
// Lookup by hex
def found = Color.fromHex("#FFFF00")
println "From hex #FFFF00: ${found} (${found.displayName})"
println ""
// Enum as configuration
def themeConfig = [
primary: Color.BLUE,
secondary: Color.GREEN,
accent: Color.PURPLE,
warning: Color.YELLOW,
error: Color.RED
]
println "Theme configuration:"
themeConfig.each { role, c ->
println " ${role.padRight(12)}: ${c.displayName} (${c.hex})"
}
// Serialize entire config
def serialized = themeConfig.collectEntries { role, c -> [role, c.name()] }
println "\nSerialized config: ${serialized}"
// Deserialize back
def deserialized = serialized.collectEntries { role, name -> [role, Color.valueOf(name)] }
assert deserialized == themeConfig
println "Deserialization verified: passed"
Output
Serialized: [name:RED, hex:#FF0000, displayName:Red, ordinal:0] [name:GREEN, hex:#00FF00, displayName:Green, ordinal:1] [name:BLUE, hex:#0000FF, displayName:Blue, ordinal:2] [name:YELLOW, hex:#FFFF00, displayName:Yellow, ordinal:3] [name:PURPLE, hex:#800080, displayName:Purple, ordinal:4] From map: BLUE (#0000FF) From hex #FFFF00: YELLOW (Yellow) Theme configuration: primary : Blue (#0000FF) secondary : Green (#00FF00) accent : Purple (#800080) warning : Yellow (#FFFF00) error : Red (#FF0000) Serialized config: [primary:BLUE, secondary:GREEN, accent:PURPLE, warning:YELLOW, error:RED] Deserialization verified: passed
What happened here: Since enums often need to be stored or transmitted, having clean serialization patterns is important. The toMap() method creates a map representation suitable for JSON. The static fromMap() and fromHex() methods provide reverse lookups. For simple serialization, name() and valueOf() are the standard pair – they’re guaranteed to be consistent. For a dedicated guide on string-to-enum conversion, see our String to Enum post.
Example 11: Enum with next() and previous()
What we’re doing: Using Groovy’s next() and previous() methods for enum navigation and ranges.
Example 11: Enum next() and previous()
enum DayOfWeek {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
boolean isWeekday() { this in MONDAY..FRIDAY }
boolean isWeekend() { this in [SATURDAY, SUNDAY] }
}
// next() and previous()
def today = DayOfWeek.WEDNESDAY
println "Today: ${today}"
println "Tomorrow: ${today.next()}"
println "Yesterday: ${today.previous()}"
println ""
// Enum ranges (uses next() internally)
def weekdays = DayOfWeek.MONDAY..DayOfWeek.FRIDAY
println "Weekdays: ${weekdays}"
def weekend = [DayOfWeek.SATURDAY, DayOfWeek.SUNDAY]
println "Weekend: ${weekend}"
println ""
// Iterate through the week
println "Week schedule:"
DayOfWeek.values().each { day ->
def type = day.isWeekday() ? "Work" : "Rest"
println " ${day.name().padRight(10)} : ${type}"
}
println ""
// Use in switch with range
def schedule(DayOfWeek day) {
switch (day) {
case DayOfWeek.MONDAY..DayOfWeek.THURSDAY:
return "Regular work day"
case DayOfWeek.FRIDAY:
return "Casual Friday!"
case [DayOfWeek.SATURDAY, DayOfWeek.SUNDAY]:
return "Weekend off"
}
}
DayOfWeek.values().each { day ->
println "${day.name().padRight(10)}: ${schedule(day)}"
}
println ""
// Wrap-around navigation
def current = DayOfWeek.SUNDAY
println "Sunday.next() = ${current.next()}" // Wraps to MONDAY
current = DayOfWeek.MONDAY
println "Monday.previous() = ${current.previous()}" // Wraps to SUNDAY
Output
Today: WEDNESDAY Tomorrow: THURSDAY Yesterday: TUESDAY Weekdays: [MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY] Weekend: [SATURDAY, SUNDAY] Week schedule: MONDAY : Work TUESDAY : Work WEDNESDAY : Work THURSDAY : Work FRIDAY : Work SATURDAY : Rest SUNDAY : Rest MONDAY : Regular work day TUESDAY : Regular work day WEDNESDAY : Regular work day THURSDAY : Regular work day FRIDAY : Casual Friday! SATURDAY : Weekend off SUNDAY : Weekend off Sunday.next() = MONDAY Monday.previous() = SUNDAY
What happened here: Groovy adds next() and previous() to all enums, enabling the .. range operator. This means you can create ranges of enum values – MONDAY..FRIDAY gives you all five weekdays. The navigation wraps around: SUNDAY.next() returns MONDAY. Enum ranges work in switch cases too, making day-of-week logic clean and readable. This is pure Groovy – Java doesn’t have this.
Example 12: Real-World Enum Patterns
What we’re doing: Practical enum patterns you’ll use in production Groovy applications.
Example 12: Real-World Patterns
// Pattern 1: Configuration enum
enum Environment {
DEV("localhost", 8080, true),
STAGING("staging.example.com", 443, true),
PRODUCTION("api.example.com", 443, false)
final String host
final int port
final boolean debugEnabled
Environment(String host, int port, boolean debugEnabled) {
this.host = host
this.port = port
this.debugEnabled = debugEnabled
}
String getBaseUrl() {
def protocol = port == 443 ? "https" : "http"
def portSuffix = (port == 80 || port == 443) ? "" : ":${port}"
"${protocol}://${host}${portSuffix}"
}
}
println "Environments:"
Environment.values().each { env ->
println " ${env.name().padRight(12)} : ${env.baseUrl} (debug=${env.debugEnabled})"
}
println ""
// Pattern 2: Logging level with severity
enum LogLevel {
TRACE(0), DEBUG(1), INFO(2), WARN(3), ERROR(4), FATAL(5)
final int severity
LogLevel(int severity) {
this.severity = severity
}
boolean isEnabledFor(LogLevel threshold) {
this.severity >= threshold.severity
}
}
def currentLevel = LogLevel.WARN
LogLevel.values().each { level ->
def enabled = level.isEnabledFor(currentLevel)
println " ${level.name().padRight(6)} severity=${level.severity} enabled=${enabled}"
}
println ""
// Pattern 3: Enum singleton
enum AppConfig {
INSTANCE
private Map settings = [:]
void set(String key, Object value) { settings[key] = value }
Object get(String key) { settings[key] }
Map getAll() { settings.asImmutable() }
}
AppConfig.INSTANCE.set("app.name", "TechnoScripts")
AppConfig.INSTANCE.set("app.version", "2.0")
AppConfig.INSTANCE.set("app.debug", true)
println "Config singleton:"
AppConfig.INSTANCE.all.each { k, v ->
println " ${k} = ${v}"
}
println ""
// Pattern 4: Enum-driven factory
enum NotificationType {
EMAIL { String send(String to, String msg) { "Email sent to ${to}: ${msg}" } },
SMS { String send(String to, String msg) { "SMS sent to ${to}: ${msg}" } },
PUSH { String send(String to, String msg) { "Push notification to ${to}: ${msg}" } },
SLACK { String send(String to, String msg) { "Slack message to #${to}: ${msg}" } }
abstract String send(String to, String message)
}
println "Sending notifications:"
NotificationType.values().each { type ->
println " ${type.send('admin', 'Server restarted')}"
}
Output
Environments: DEV : http://localhost:8080 (debug=true) STAGING : https://staging.example.com (debug=true) PRODUCTION : https://api.example.com (debug=false) TRACE severity=0 enabled=false DEBUG severity=1 enabled=false INFO severity=2 enabled=false WARN severity=3 enabled=true ERROR severity=4 enabled=true FATAL severity=5 enabled=true Config singleton: app.name = TechnoScripts app.version = 2.0 app.debug = true Sending notifications: Email sent to admin: Server restarted SMS sent to admin: Server restarted Push notification to admin: Server restarted Slack message to #admin: Server restarted
What happened here: Four production-ready patterns. The Configuration enum stores per-environment settings with computed properties. The LogLevel enum uses severity ordering for threshold-based filtering. The Singleton enum is the recommended way to implement a singleton in Java/Groovy – it’s thread-safe and serialization-safe by default. The Factory enum uses abstract methods to create a notification dispatch system where each type handles its own sending logic.
Enum vs Constants vs Maps
When should you use an enum versus other approaches?
| Approach | Type Safe | Can Have Methods | Use When |
|---|---|---|---|
| Enum | Yes | Yes | Fixed set of related constants with behavior |
| String constants | No | No | Simple labels with no behavior needed |
| Integer constants | No | No | Legacy code, bitwise flags |
| Map of closures | No | Sort of | Dynamic, configurable strategies |
| Sealed classes | Yes | Yes | When constants need different properties/shapes |
Rule of thumb: If you have a fixed set of values known at compile time, use an enum. If the set can change at runtime, use a map or collection. If each value has significantly different structure, consider sealed classes or a class hierarchy.
Groovy Enhancements for Enums
Groovy adds several capabilities to enums beyond what Java provides:
next()andprevious()– navigate between enum values with wrap-around- Range operator
..– create ranges of enum values - Closure properties – store closures as enum fields
- GDK collection methods – use
findAll,collect,groupByonvalues() - Spread operator
*.– extract properties from all enum constants at once - Switch with lists and ranges – match multiple enum values in a single case
inoperator – check enum membership in collections naturally
Common Enum Patterns
Common Enum Patterns Quick Reference
// 1. Lookup map for O(1) access by property
enum Currency {
USD('$', "US Dollar"), EUR('E', "Euro"), GBP('L', "British Pound")
final String symbol; final String fullName
Currency(String s, String f) { this.symbol = s; this.fullName = f }
private static final Map<String, Currency> BY_SYMBOL =
values().collectEntries { [it.symbol, it] }
static Currency bySymbol(String s) { BY_SYMBOL[s] }
}
println "Symbol lookup: ${Currency.bySymbol('$')}"
// 2. Enum with default value
enum Theme {
LIGHT, DARK, SYSTEM
static Theme fromString(String s) {
try { valueOf(s?.toUpperCase()) } catch (e) { SYSTEM }
}
}
println "Theme: ${Theme.fromString('dark')}, Default: ${Theme.fromString('invalid')}"
// 3. Bitwise flag enum
enum FilePermission {
READ(4), WRITE(2), EXECUTE(1)
final int bit
FilePermission(int bit) { this.bit = bit }
static int combine(FilePermission... perms) { perms*.bit.sum() }
static List<FilePermission> fromMask(int mask) {
values().findAll { (mask & it.bit) != 0 }
}
}
def rwx = FilePermission.combine(FilePermission.READ, FilePermission.WRITE, FilePermission.EXECUTE)
println "rwx = ${rwx}, decoded: ${FilePermission.fromMask(rwx)}"
println "rw- = ${FilePermission.fromMask(6)}"
Output
Symbol lookup: USD Theme: DARK, Default: SYSTEM rwx = 7, decoded: [READ, WRITE, EXECUTE] rw- = [READ, WRITE]
Best Practices
DO:
- Use enums for any fixed set of related values – statuses, types, categories, configurations
- Add properties and methods to enums when each constant has associated data or behavior
- Use
name()for serialization andvalueOf()for deserialization – they’re guaranteed to match - Create static lookup methods (like
fromCode()) for reverse lookups by property - use Groovy’s
next()/previous()and range support for sequential enums - Use
EnumSetandEnumMapfor collections of enum values
DON’T:
- Depend on
ordinal()for persistence – it changes if you reorder constants - Use string comparison (
status == "ACTIVE") when an enum would be type-safe - Create enums with hundreds of constants – consider a database or configuration file instead
- Add mutable state to enums – keep enum instances immutable for thread safety
- Use
valueOf()without handlingIllegalArgumentException– it throws if the name doesn’t match
Conclusion
We covered the key techniques for Groovy enums – from simple constant declarations to enums with properties, methods, constructors, interfaces, closures, state machines, and the Groovy-specific enhancements that make them more powerful than Java enums.
The key insight is that enums are much more than named constants. They’re full-fledged classes that can encapsulate behavior, implement interfaces, and serve as strategy objects. Groovy makes them even better with next()/previous(), range support, and closure properties. Whenever you find yourself using strings or integers to represent a fixed set of options, reach for an enum instead.
For converting strings to enums at runtime, check out our dedicated Convert String to Enum guide. And for understanding dynamic typing alongside enums, see our def keyword guide.
Summary
- Enums define a fixed, type-safe set of constants – no more magic strings
- Enums can have properties, constructors, methods, and implement interfaces
- Groovy adds
next()/previous(), ranges, and closure support to enums - Use
name()/valueOf()for serialization, notordinal() - GDK collection methods like
findAll,collect, andgroupBywork on enum arrays
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: Convert String to Enum in Groovy
Frequently Asked Questions
Can Groovy enums have constructors?
Yes, Groovy enums can have constructors, but they must be private or package-private. Each enum constant passes arguments to the constructor when it’s declared. This lets you associate data like codes, labels, or configuration values with each constant.
What is the difference between name() and toString() in Groovy enums?
The name() method always returns the exact constant name as declared (e.g., ‘NORTH’). The toString() method returns the same by default but can be overridden to return something different (e.g., ‘North’). For serialization, always use name() because it’s guaranteed to match valueOf().
Can Groovy enums implement interfaces?
Yes, Groovy enums can implement one or more interfaces. Each enum constant can provide its own implementation of the interface methods, creating a clean strategy pattern. This is especially useful for polymorphic behavior where each constant has different logic.
How do I safely convert a string to an enum in Groovy?
Use valueOf() with exception handling, or create a static lookup method. For example: static MyEnum fromString(String s) { try { valueOf(s.toUpperCase()) } catch (e) { null } }. See our dedicated Convert String to Enum guide for more patterns.
What does next() and previous() do on Groovy enums?
Groovy automatically adds next() and previous() methods to all enums. next() returns the next constant in declaration order, wrapping from the last to the first. previous() does the reverse. These methods enable the range operator (..) to work with enums, so you can write expressions like DayOfWeek.MONDAY..DayOfWeek.FRIDAY.
Related Posts
Previous in Series: Groovy Assert and Power Assert – Testing Values
Next in Series: Convert String to Enum in Groovy
Related Topics You Might Like:
- Convert String to Enum in Groovy
- Groovy def Keyword – Dynamic Typing Explained
- Groovy Assert and Power Assert – Testing Values
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment