Groovy Enum Complete Guide – Constants, Methods, Best Practices with 12 Examples

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.

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 switch statements, 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?

ApproachType SafeCan Have MethodsUse When
EnumYesYesFixed set of related constants with behavior
String constantsNoNoSimple labels with no behavior needed
Integer constantsNoNoLegacy code, bitwise flags
Map of closuresNoSort ofDynamic, configurable strategies
Sealed classesYesYesWhen 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() and previous() – 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, groupBy on values()
  • Spread operator *. – extract properties from all enum constants at once
  • Switch with lists and ranges – match multiple enum values in a single case
  • in operator – 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 and valueOf() 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 EnumSet and EnumMap for 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 handling IllegalArgumentException – 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, not ordinal()
  • GDK collection methods like findAll, collect, and groupBy work 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.

Previous in Series: Groovy Assert and Power Assert – Testing Values

Next in Series: Convert String to Enum in Groovy

Related Topics You Might Like:

This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *