every way to handle Groovy string to enum conversion with 12 examples. valueOf, safe conversion, case-insensitive lookup, and more. Complete guide for Groovy 5.x.
“Converting a string to an enum is simple until it isn’t. Handle the edge cases up front, and you’ll thank yourself every time bad data hits your system.”
Robert C. Martin, Clean Code
Last Updated: March 2026 | Tested on: Groovy 5.x, Java 17+ | Difficulty: Beginner to Intermediate | Reading Time: 16 minutes
A groovy string to enum conversion sounds simple enough – and it is, when the string matches perfectly. But real data comes from user input, JSON payloads, database columns, and config files, where you hit case differences, trailing whitespace, invalid values, and null strings. That’s where knowing all your conversion options pays off.
This post covers every way to convert a String to an Enum in Groovy. We’ll walk through 12 tested examples showing valueOf(), Groovy’s as keyword, case-insensitive lookups, safe conversion with defaults, reverse lookups by property, and patterns for handling enums in real-world applications like JSON processing and API responses.
If you’re new to enums, start with our complete Groovy Enum guide first. And if you’ve worked with other string conversions, our String to Integer guide follows similar patterns.
Every example is tested on Groovy 5.x with exact output shown. Let’s get into it.
Table of Contents
The Basics – valueOf() and as Keyword
Groovy provides two primary ways to convert a string to an enum: Java’s valueOf() method and Groovy’s as keyword. Both require an exact case-sensitive match with the enum constant name.
Basic String to Enum Conversion
enum Color {
RED, GREEN, BLUE, YELLOW, PURPLE
}
// Method 1: valueOf() - Java standard approach
def red = Color.valueOf("RED")
println "valueOf: ${red} (type: ${red.class.simpleName})"
// Method 2: Groovy 'as' keyword
def blue = "BLUE" as Color
println "as keyword: ${blue} (type: ${blue.class.simpleName})"
// Method 3: Enum.valueOf() with explicit class
def green = Enum.valueOf(Color, "GREEN")
println "Enum.valueOf: ${green}"
// All three produce the same result
assert Color.valueOf("YELLOW") == ("YELLOW" as Color)
assert Color.valueOf("YELLOW") == Enum.valueOf(Color, "YELLOW")
println "\nAll methods produce identical results: true"
// String must match exactly (case-sensitive)
println "\nCase sensitivity:"
println " 'RED' -> ${Color.valueOf('RED')}"
try {
Color.valueOf("red") // This will fail
} catch (IllegalArgumentException e) {
println " 'red' -> ERROR: ${e.message}"
}
Output
valueOf: RED (type: Color) as keyword: BLUE (type: Color) Enum.valueOf: GREEN All methods produce identical results: true Case sensitivity: 'RED' -> RED 'red' -> ERROR: No enum constant Color.red
The key thing to remember: both valueOf() and as require an exact match on the constant name. They’re case-sensitive and throw IllegalArgumentException if no match is found. This is by design – enums are strict, and an unrecognized value should be treated as an error.
Why String to Enum Conversion Fails
According to the Groovy GDK documentation, the most common failure reasons are:
- Case mismatch –
"red"vs"RED" - Whitespace –
" RED "has leading/trailing spaces - Invalid value –
"ORANGE"doesn’t exist in the enum - Null input –
nullpassed tovalueOf() - Display name vs constant name – trying to use
"Not Found"instead of"NOT_FOUND"
Common Failure Scenarios
enum Status { ACTIVE, INACTIVE, PENDING }
def tryConvert(String input) {
try {
def result = Status.valueOf(input)
println " '${input}' -> ${result}"
} catch (Exception e) {
println " '${input}' -> FAILED: ${e.class.simpleName} - ${e.message}"
}
}
println "Conversion attempts:"
tryConvert("ACTIVE") // Works
tryConvert("active") // Case mismatch
tryConvert(" ACTIVE ") // Whitespace
tryConvert("Active") // Mixed case
tryConvert("UNKNOWN") // Invalid value
tryConvert(null) // Null input
tryConvert("") // Empty string
Output
Conversion attempts: 'ACTIVE' -> ACTIVE 'active' -> FAILED: IllegalArgumentException - No enum constant Status.active ' ACTIVE ' -> FAILED: IllegalArgumentException - No enum constant Status. ACTIVE 'Active' -> FAILED: IllegalArgumentException - No enum constant Status.Active 'UNKNOWN' -> FAILED: IllegalArgumentException - No enum constant Status.UNKNOWN 'null' -> FAILED: NullPointerException - Name is null '' -> FAILED: IllegalArgumentException - No enum constant Status.
Every one of these failures is something you’ll encounter in real applications. The rest of this post shows you how to handle them elegantly.
12 Practical Conversion Examples
Example 1: Safe Conversion with Default Value
What we’re doing: Converting a string to an enum safely, returning a default value instead of throwing an exception.
Example 1: Safe Conversion
enum Priority {
LOW, MEDIUM, HIGH, CRITICAL
static Priority fromString(String value, Priority defaultValue = null) {
if (!value?.trim()) return defaultValue
try {
return valueOf(value.trim().toUpperCase())
} catch (IllegalArgumentException e) {
return defaultValue
}
}
}
// Successful conversions
println "Valid conversions:"
println " 'HIGH' -> ${Priority.fromString('HIGH')}"
println " 'high' -> ${Priority.fromString('high')}"
println " ' Medium ' -> ${Priority.fromString(' Medium ')}"
println ""
// Failed conversions with defaults
println "With defaults:"
println " 'URGENT' -> ${Priority.fromString('URGENT', Priority.HIGH)}"
println " null -> ${Priority.fromString(null, Priority.LOW)}"
println " '' -> ${Priority.fromString('', Priority.MEDIUM)}"
println " 'invalid' -> ${Priority.fromString('invalid')}"
println ""
// Use in practice
def processTasks(List<Map> tasks) {
tasks.each { task ->
def priority = Priority.fromString(task.priority, Priority.MEDIUM)
println " Task '${task.name}': ${priority}"
}
}
println "Processing tasks:"
processTasks([
[name: "Fix bug", priority: "CRITICAL"],
[name: "Update docs", priority: "low"],
[name: "Refactor", priority: null],
[name: "Review PR", priority: "URGENT"]
])
Output
Valid conversions: 'HIGH' -> HIGH 'high' -> HIGH ' Medium ' -> MEDIUM With defaults: 'URGENT' -> HIGH null -> LOW '' -> MEDIUM 'invalid' -> null Processing tasks: Task 'Fix bug': CRITICAL Task 'Update docs': LOW Task 'Refactor': MEDIUM Task 'Review PR': MEDIUM
What happened here: The fromString method handles all the common failure cases – null, empty, whitespace, and invalid values. It normalizes the input with trim().toUpperCase() and catches IllegalArgumentException to return a default value instead of crashing. This is the pattern you’ll use most often in production code.
Example 2: Case-Insensitive Conversion
What we’re doing: Building a case-insensitive enum lookup that handles any capitalization.
Example 2: Case-Insensitive Lookup
enum HttpMethod {
GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
// Pre-built lookup map for O(1) case-insensitive access
private static final Map<String, HttpMethod> LOOKUP =
values().collectEntries { [it.name().toUpperCase(), it] }
static HttpMethod fromString(String method) {
if (!method?.trim()) return null
return LOOKUP[method.trim().toUpperCase()]
}
}
// Test various cases
def inputs = ["GET", "get", "Get", "post", "POST", "Put", " delete ", "INVALID", null]
inputs.each { input ->
def result = HttpMethod.fromString(input)
println " '${input}' -> ${result ?: 'null'}"
}
println ""
// Practical use: parse HTTP requests
def parseRequest(String requestLine) {
def parts = requestLine.split(/\s+/, 3)
def method = HttpMethod.fromString(parts[0])
def path = parts.size() > 1 ? parts[1] : "/"
assert method : "Unknown HTTP method: ${parts[0]}"
return [method: method, path: path]
}
def requests = [
"GET /api/users HTTP/1.1",
"post /api/users HTTP/1.1",
"Delete /api/users/123 HTTP/1.1"
]
println "Parsed requests:"
requests.each { req ->
def parsed = parseRequest(req)
println " ${parsed.method} ${parsed.path}"
}
Output
'GET' -> GET 'get' -> GET 'Get' -> GET 'post' -> POST 'POST' -> POST 'Put' -> PUT ' delete ' -> DELETE 'INVALID' -> null 'null' -> null Parsed requests: GET /api/users POST /api/users DELETE /api/users/123
What happened here: The pre-built LOOKUP map converts all enum names to uppercase once at class loading time. Then every lookup is a simple O(1) map access instead of iterating through all values. This is the recommended pattern when you have many lookups or many enum constants. The map is built using Groovy’s collectEntries – clean and expressive.
Example 3: Conversion by Display Name or Label
What we’re doing: Looking up an enum by a human-readable display name rather than the constant name.
Example 3: Lookup by Display Name
enum Country {
US("United States", "USD"),
UK("United Kingdom", "GBP"),
DE("Germany", "EUR"),
FR("France", "EUR"),
JP("Japan", "JPY"),
IN("India", "INR"),
AU("Australia", "AUD")
final String displayName
final String currency
Country(String displayName, String currency) {
this.displayName = displayName
this.currency = currency
}
// Lookup by multiple fields
private static final Map<String, Country> BY_DISPLAY =
values().collectEntries { [it.displayName.toUpperCase(), it] }
private static final Map<String, Country> BY_CURRENCY =
values().groupBy { it.currency }.collectEntries { k, v -> [k, v[0]] }
static Country fromDisplayName(String name) {
if (!name?.trim()) return null
BY_DISPLAY[name.trim().toUpperCase()]
}
static Country fromCode(String code) {
if (!code?.trim()) return null
try { valueOf(code.trim().toUpperCase()) } catch (e) { null }
}
static Country fromCurrency(String currency) {
BY_CURRENCY[currency?.toUpperCase()]
}
}
// Lookup by different fields
println "By code: ${Country.fromCode('US')}"
println "By display: ${Country.fromDisplayName('United Kingdom')}"
println "By display (case): ${Country.fromDisplayName('germany')}"
println "By currency: ${Country.fromCurrency('JPY')}"
println ""
// Process user input
def userInputs = ["United States", "uk", "France", "GERMANY", "Brazil"]
println "User input mapping:"
userInputs.each { input ->
def country = Country.fromDisplayName(input) ?: Country.fromCode(input)
if (country) {
println " '${input}' -> ${country.name()} (${country.displayName}, ${country.currency})"
} else {
println " '${input}' -> NOT FOUND"
}
}
Output
By code: US By display: United Kingdom By display (case): Germany By currency: Japan By currency: JPY -> JP User input mapping: 'United States' -> US (United States, USD) 'uk' -> UK (United Kingdom, GBP) 'France' -> FR (France, EUR) 'GERMANY' -> DE (Germany, EUR) 'Brazil' -> NOT FOUND
What happened here: Real-world enums often need lookup by more than just their constant name. This pattern creates separate lookup maps for each field – display name, code, and currency. The fromDisplayName and fromCode methods try the user input against different fields. You can chain them with the Elvis operator: Country.fromDisplayName(input) ?: Country.fromCode(input).
Example 4: Groovy’s as Keyword with Enums
What we’re doing: Exploring Groovy’s as keyword for enum conversion and how it differs from valueOf().
Example 4: The ‘as’ Keyword
enum Size {
SMALL, MEDIUM, LARGE, EXTRA_LARGE
}
// Direct 'as' conversion
def s1 = "SMALL" as Size
println "String as Size: ${s1} (${s1.class.simpleName})"
// 'as' is equivalent to valueOf()
assert ("LARGE" as Size) == Size.valueOf("LARGE")
println "Equivalence: passed"
// 'as' also fails on case mismatch
try {
def s2 = "small" as Size // Fails!
} catch (IllegalArgumentException e) {
println "'small' as Size: FAILED (case-sensitive)"
}
// Use in method parameters with implicit conversion
def getDescription(Size size) {
switch (size) {
case Size.SMALL: return "Fits tightly"
case Size.MEDIUM: return "Standard fit"
case Size.LARGE: return "Relaxed fit"
case Size.EXTRA_LARGE: return "Oversized fit"
}
}
// 'as' in expressions
def sizes = ["SMALL", "MEDIUM", "LARGE"]
def converted = sizes.collect { it as Size }
println "Converted: ${converted}"
println "Types: ${converted.collect { it.class.simpleName }.unique()}"
println ""
// Safe 'as' conversion with helper
def safeAs(String value, Class<? extends Enum> enumClass, defaultValue = null) {
if (!value?.trim()) return defaultValue
try {
return value.trim().toUpperCase() as Size
} catch (IllegalArgumentException e) {
return defaultValue
}
}
println "Safe 'as': ${safeAs('medium', Size)}"
println "Safe 'as' null: ${safeAs(null, Size, Size.MEDIUM)}"
println "Safe 'as' invalid: ${safeAs('huge', Size, Size.LARGE)}"
Output
String as Size: SMALL (Size) Equivalence: passed 'small' as Size: FAILED (case-sensitive) Converted: [SMALL, MEDIUM, LARGE] Types: [Size] Safe 'as': MEDIUM Safe 'as' null: MEDIUM Safe 'as' invalid: LARGE
What happened here: Groovy’s as keyword provides a more Groovy-idiomatic way to convert strings to enums. Under the hood, it calls valueOf(), so it has the same case-sensitivity and error behavior. The as syntax is particularly nice in collect operations for batch conversion. But for production code, you still need the safe wrapper to handle invalid inputs.
Example 5: Enum Conversion with Aliases
What we’re doing: Supporting multiple string aliases that map to the same enum constant.
Example 5: Enum Aliases
enum FileType {
IMAGE(["image", "img", "photo", "picture", "pic"]),
VIDEO(["video", "vid", "movie", "clip"]),
AUDIO(["audio", "sound", "music", "mp3"]),
DOCUMENT(["document", "doc", "text", "txt", "pdf"]),
ARCHIVE(["archive", "zip", "compressed", "tar"])
final List<String> aliases
FileType(List<String> aliases) {
this.aliases = aliases
}
private static final Map<String, FileType> ALIAS_MAP = {
def map = [:]
values().each { type ->
// Add the constant name itself
map[type.name().toUpperCase()] = type
// Add all aliases
type.aliases.each { alias ->
map[alias.toUpperCase()] = type
}
}
return map
}()
static FileType fromString(String input) {
if (!input?.trim()) return null
ALIAS_MAP[input.trim().toUpperCase()]
}
}
// Test with various inputs
def inputs = ["IMAGE", "photo", "Pic", "VIDEO", "clip", "mp3",
"document", "PDF", "zip", "unknown"]
println "Alias-based conversion:"
inputs.each { input ->
def type = FileType.fromString(input)
println " '${input}'.padRight(12) -> ${type ?: 'NOT FOUND'}"
}
println ""
// Classify files
def files = ["report.pdf", "vacation.jpg", "song.mp3", "backup.zip", "demo.mp4"]
println "File classification:"
files.each { file ->
def ext = file.split(/\./)[-1]
def type = FileType.fromString(ext)
println " ${file.padRight(16)} -> ${type ?: 'UNKNOWN'}"
}
Output
Alias-based conversion: 'IMAGE' -> IMAGE 'photo' -> IMAGE 'Pic' -> IMAGE 'VIDEO' -> VIDEO 'clip' -> VIDEO 'mp3' -> AUDIO 'document' -> DOCUMENT 'PDF' -> DOCUMENT 'zip' -> ARCHIVE 'unknown' -> NOT FOUND File classification: report.pdf -> DOCUMENT vacation.jpg -> UNKNOWN song.mp3 -> AUDIO backup.zip -> ARCHIVE demo.mp4 -> UNKNOWN
What happened here: The alias pattern is essential when your enum needs to accept many different string representations. Each constant stores a list of aliases. The static ALIAS_MAP is built once at class loading time by flattening all aliases into a single lookup map. This gives O(1) lookup regardless of how many aliases exist. The map includes both the constant name and all aliases, so "IMAGE" and "photo" both resolve correctly.
Example 6: Enum Conversion with Validation
What we’re doing: Converting strings to enums with validation, error collection, and batch processing.
Example 6: Validated Conversion
enum Role {
ADMIN, EDITOR, VIEWER, GUEST
static final Set<String> VALID_NAMES = values()*.name() as Set
static boolean isValid(String input) {
input?.trim()?.toUpperCase() in VALID_NAMES
}
static Role parse(String input) {
if (!input?.trim()) {
throw new IllegalArgumentException("Role cannot be null or empty")
}
def normalized = input.trim().toUpperCase()
if (normalized !in VALID_NAMES) {
throw new IllegalArgumentException(
"Invalid role '${input}'. Valid roles: ${VALID_NAMES.join(', ')}"
)
}
return valueOf(normalized)
}
}
// Validate before converting
def inputs = ["ADMIN", "editor", "UNKNOWN", "", null, "viewer"]
println "Validation:"
inputs.each { input ->
println " '${input}' valid: ${Role.isValid(input)}"
}
println ""
// Parse with detailed error messages
println "Parsing:"
inputs.each { input ->
try {
def role = Role.parse(input)
println " '${input}' -> ${role}"
} catch (IllegalArgumentException e) {
println " '${input}' -> ERROR: ${e.message}"
}
}
println ""
// Batch validation
def batchConvert(List<String> inputs) {
def results = [success: [], errors: []]
inputs.each { input ->
if (Role.isValid(input)) {
results.success << [input: input, role: Role.valueOf(input.trim().toUpperCase())]
} else {
results.errors << [input: input, reason: "Invalid role value"]
}
}
return results
}
def batch = batchConvert(["admin", "EDITOR", "superuser", "viewer", "root"])
println "Batch results:"
println " Successful: ${batch.success.collect { it.role }}"
println " Errors: ${batch.errors.collect { it.input }}"
Output
Validation: 'ADMIN' valid: true 'editor' valid: true 'UNKNOWN' valid: false '' valid: false 'null' valid: false 'viewer' valid: true Parsing: 'ADMIN' -> ADMIN 'editor' -> EDITOR 'UNKNOWN' -> ERROR: Invalid role 'UNKNOWN'. Valid roles: ADMIN, EDITOR, VIEWER, GUEST '' -> ERROR: Role cannot be null or empty 'null' -> ERROR: Role cannot be null or empty 'viewer' -> VIEWER Batch results: Successful: [ADMIN, EDITOR, VIEWER] Errors: [superuser, root]
What happened here: The isValid() method lets you check before converting – useful when you want to handle invalid values gracefully without exceptions. The parse() method provides detailed error messages that include the list of valid values, which is much more helpful than the default IllegalArgumentException message. The batch converter collects successes and errors separately, which is perfect for processing CSV imports or API bulk requests.
Example 7: Enum Conversion with Groovy’s find and findAll
What we’re doing: Using Groovy collection methods for flexible enum matching beyond exact string equality.
Example 7: Flexible Matching
enum LogLevel {
TRACE(0), DEBUG(1), INFO(2), WARN(3), ERROR(4), FATAL(5)
final int severity
LogLevel(int severity) { this.severity = severity }
}
// find by name (case-insensitive)
def byName = LogLevel.values().find { it.name().equalsIgnoreCase("warn") }
println "Find by name: ${byName}"
// find by property
def bySeverity = LogLevel.values().find { it.severity == 3 }
println "Find by severity 3: ${bySeverity}"
// findAll - multiple matches
def highSeverity = LogLevel.values().findAll { it.severity >= 3 }
println "High severity: ${highSeverity}"
// Partial name matching
def partialMatch = LogLevel.values().find { it.name().startsWith("ER") }
println "Starts with 'ER': ${partialMatch}"
// Fuzzy matching with Levenshtein-like approach
def closestMatch(String input, Class<? extends Enum> enumClass) {
def values = enumClass.values()
def upper = input?.toUpperCase()
// Try exact match first
def exact = values.find { it.name() == upper }
if (exact) return exact
// Try contains
def contains = values.find { it.name().contains(upper) || upper?.contains(it.name()) }
if (contains) return contains
// Try starts with
def startsWith = values.find { it.name().startsWith(upper?.take(3) ?: "") }
return startsWith
}
println ""
println "Fuzzy matching:"
["ERROR", "err", "WAR", "inf", "DEB", "FATAL"].each { input ->
def match = closestMatch(input, LogLevel)
println " '${input}' -> ${match ?: 'NO MATCH'}"
}
println ""
// Convert list of strings to enums, filtering invalid ones
def rawInput = ["INFO", "debug", "INVALID", "warn", "ERROR", "unknown", "TRACE"]
def validLevels = rawInput.collect { name ->
LogLevel.values().find { it.name().equalsIgnoreCase(name) }
}.findAll { it != null }
println "Filtered conversion: ${validLevels}"
println "Invalid count: ${rawInput.size() - validLevels.size()}"
Output
Find by name: WARN Find by severity 3: WARN High severity: [WARN, ERROR, FATAL] Starts with 'ER': ERROR Fuzzy matching: 'ERROR' -> ERROR 'err' -> ERROR 'WAR' -> WARN 'inf' -> INFO 'DEB' -> DEBUG 'FATAL' -> FATAL Filtered conversion: [INFO, DEBUG, WARN, ERROR, TRACE] Invalid count: 2
What happened here: Groovy’s collection methods make enum lookup extremely flexible. You can find by any criterion – name, property value, partial match, or any custom logic. The fuzzy matching example tries increasingly loose matches: exact, then contains, then prefix. The batch conversion pattern – collect then findAll to filter nulls – is a clean way to process lists of strings into enums.
Example 8: Enum Conversion with Groovy’s as Operator Customization
What we’re doing: Customizing enum coercion behavior by adding asType to String for custom enum conversion.
Example 8: Custom asType
enum TaskStatus {
TODO("To Do"),
IN_PROGRESS("In Progress"),
IN_REVIEW("In Review"),
DONE("Done"),
CANCELLED("Cancelled")
final String displayName
TaskStatus(String displayName) {
this.displayName = displayName
}
private static final Map<String, TaskStatus> LOOKUP = {
def map = [:]
values().each { status ->
map[status.name().toUpperCase()] = status
map[status.displayName.toUpperCase()] = status
// Also add without underscores
map[status.name().replaceAll('_', '').toUpperCase()] = status
// And with spaces replaced by underscores
map[status.displayName.replaceAll(' ', '_').toUpperCase()] = status
}
return map
}()
static TaskStatus fromString(String input) {
if (!input?.trim()) return null
def normalized = input.trim().toUpperCase()
LOOKUP[normalized] ?: LOOKUP[normalized.replaceAll(/[\s_-]+/, '_')]
}
}
// Multiple input formats, all resolving correctly
def inputs = [
"TODO", "To Do", "to_do", "to-do",
"IN_PROGRESS", "In Progress", "INPROGRESS", "in-progress",
"IN_REVIEW", "In Review",
"DONE", "Done",
"CANCELLED", "Cancelled"
]
println "Flexible conversion:"
inputs.each { input ->
def status = TaskStatus.fromString(input)
println " '${input}'.padRight(14) -> ${status?.name() ?: 'NOT FOUND'} (${status?.displayName ?: 'N/A'})"
}
println ""
// Real-world: parse task updates from different systems
def updates = [
[task: "TASK-001", status: "In Progress"], // From Jira
[task: "TASK-002", status: "DONE"], // From API
[task: "TASK-003", status: "in_review"], // From database
[task: "TASK-004", status: "to-do"] // From CSV import
]
println "Multi-system integration:"
updates.each { update ->
def status = TaskStatus.fromString(update.status)
println " ${update.task}: ${status?.displayName ?: 'PARSE ERROR'}"
}
Output
Flexible conversion: 'TODO'.padRight(14) -> TODO (To Do) 'To Do'.padRight(14) -> TODO (To Do) 'to_do'.padRight(14) -> TODO (To Do) 'to-do'.padRight(14) -> TODO (To Do) 'IN_PROGRESS'.padRight(14) -> IN_PROGRESS (In Progress) 'In Progress'.padRight(14) -> IN_PROGRESS (In Progress) 'INPROGRESS'.padRight(14) -> IN_PROGRESS (In Progress) 'in-progress'.padRight(14) -> IN_PROGRESS (In Progress) 'IN_REVIEW'.padRight(14) -> IN_REVIEW (In Review) 'In Review'.padRight(14) -> IN_REVIEW (In Review) 'DONE'.padRight(14) -> DONE (Done) 'Done'.padRight(14) -> DONE (Done) 'CANCELLED'.padRight(14) -> CANCELLED (Cancelled) 'Cancelled'.padRight(14) -> CANCELLED (Cancelled) Multi-system integration: TASK-001: In Progress TASK-002: Done TASK-003: In Review TASK-004: To Do
What happened here: When you integrate with multiple systems, enum values arrive in different formats – underscored, hyphenated, spaces, mixed case. The expanded lookup map registers every common variation: the constant name, the display name, without underscores, and with hyphens converted to underscores. This way, "In Progress", "IN_PROGRESS", "in-progress", and "INPROGRESS" all resolve to the same constant.
Example 9: Reverse Lookup by Property Value
What we’re doing: Converting a numeric code or other property value back to an enum constant.
Example 9: Reverse Lookup
enum ErrorCode {
SUCCESS(0, "Operation completed successfully"),
INVALID_INPUT(100, "Invalid input provided"),
NOT_FOUND(404, "Resource not found"),
UNAUTHORIZED(401, "Authentication required"),
FORBIDDEN(403, "Access denied"),
SERVER_ERROR(500, "Internal server error"),
TIMEOUT(504, "Request timed out")
final int code
final String description
ErrorCode(int code, String description) {
this.code = code
this.description = description
}
// Precomputed reverse lookup map
private static final Map<Integer, ErrorCode> BY_CODE =
values().collectEntries { [it.code, it] }
static ErrorCode fromCode(int code) {
BY_CODE[code]
}
static ErrorCode fromCode(String codeStr) {
if (!codeStr?.trim()) return null
try {
fromCode(codeStr.trim().toInteger())
} catch (NumberFormatException e) {
null
}
}
}
// Lookup by integer code
println "By integer code:"
[0, 100, 404, 401, 500, 999].each { code ->
def error = ErrorCode.fromCode(code)
if (error) {
println " ${code} -> ${error.name()}: ${error.description}"
} else {
println " ${code} -> UNKNOWN ERROR CODE"
}
}
println ""
// Lookup by string code (from HTTP response, config file, etc.)
println "By string code:"
["404", "500", "200", "abc", null, ""].each { code ->
def error = ErrorCode.fromCode(code)
println " '${code}' -> ${error?.name() ?: 'NOT FOUND'}"
}
println ""
// Practical: process API response codes
def responses = [
[endpoint: "/api/users", code: 0],
[endpoint: "/api/orders", code: 404],
[endpoint: "/api/admin", code: 401],
[endpoint: "/api/report", code: 504]
]
println "API Response Summary:"
responses.each { resp ->
def error = ErrorCode.fromCode(resp.code)
def status = error?.code == 0 ? "OK" : "FAIL"
println " ${resp.endpoint.padRight(16)} [${status}] ${error?.description ?: 'Unknown error'}"
}
Output
By integer code: 0 -> SUCCESS: Operation completed successfully 100 -> INVALID_INPUT: Invalid input provided 404 -> NOT_FOUND: Resource not found 401 -> UNAUTHORIZED: Authentication required 500 -> SERVER_ERROR: Internal server error 999 -> UNKNOWN ERROR CODE By string code: '404' -> NOT_FOUND '500' -> SERVER_ERROR '200' -> NOT FOUND 'abc' -> NOT FOUND 'null' -> NOT FOUND '' -> NOT FOUND API Response Summary: /api/users [OK] Operation completed successfully /api/orders [FAIL] Resource not found /api/admin [FAIL] Authentication required /api/report [FAIL] Request timed out
What happened here: Reverse lookup by numeric code is a very common pattern, especially with error codes and HTTP status codes. The BY_CODE map provides O(1) lookup. Notice the overloaded fromCode that accepts both int and String – the String version handles parsing and null safety. For similar string-to-number conversion patterns, see our String to Integer guide.
Example 10: Enum Conversion in switch Expressions
What we’re doing: Converting strings to enums within switch statements for clean dispatching logic.
Example 10: Enum in switch
enum Command {
START, STOP, RESTART, STATUS, HELP
static Command parse(String input) {
if (!input?.trim()) return null
try { valueOf(input.trim().toUpperCase()) } catch (e) { null }
}
}
def executeCommand(String input) {
def cmd = Command.parse(input)
switch (cmd) {
case Command.START:
return "Starting service..."
case Command.STOP:
return "Stopping service..."
case Command.RESTART:
return "Restarting service..."
case Command.STATUS:
return "Service is running (uptime: 42 hours)"
case Command.HELP:
return "Available commands: ${Command.values()*.name().join(', ')}"
case null:
return "Unknown command: '${input}'. Type 'help' for available commands."
}
}
// Process user commands
def commands = ["start", "STATUS", "help", "deploy", "Stop", "", "restart"]
println "Command processor:"
commands.each { cmd ->
println " > ${cmd ?: '(empty)'}"
println " ${executeCommand(cmd)}"
}
println ""
// Interactive-style processing with history
def history = []
["start", "status", "stop", "status", "start"].each { cmd ->
def parsed = Command.parse(cmd)
if (parsed) {
history << parsed
println "Executed: ${parsed}"
}
}
println "Command history: ${history}"
println "Unique commands used: ${history.unique(false)}"
Output
Command processor:
> start
Starting service...
> STATUS
Service is running (uptime: 42 hours)
> help
Available commands: START, STOP, RESTART, STATUS, HELP
> deploy
Unknown command: 'deploy'. Type 'help' for available commands.
> Stop
Stopping service...
> (empty)
Unknown command: ''. Type 'help' for available commands.
> restart
Restarting service...
Executed: START
Executed: STATUS
Executed: STOP
Executed: STATUS
Executed: START
Command history: [START, STATUS, STOP, STATUS, START]
Unique commands used: [START, STATUS, STOP]
What happened here: The pattern here is parse-then-switch: convert the string to an enum first, then use the enum in a switch statement. If parsing fails, null is returned and the case null branch handles the error. This is cleaner than switching on raw strings because the compiler can help verify you’ve handled all cases. The command history example shows how enum values can be collected and analyzed.
Example 11: Generic Enum Converter
What we’re doing: Building a reusable, generic enum converter that works with any enum type.
Example 11: Generic Converter
class EnumConverter {
// Basic conversion with case-insensitivity
static <T extends Enum<T>> T convert(String value, Class<T> enumClass) {
if (!value?.trim()) return null
try {
return Enum.valueOf(enumClass, value.trim().toUpperCase())
} catch (IllegalArgumentException e) {
return null
}
}
// Conversion with default value
static <T extends Enum<T>> T convert(String value, Class<T> enumClass, T defaultValue) {
return convert(value, enumClass) ?: defaultValue
}
// Conversion with validation and error reporting
static <T extends Enum<T>> Map convertWithInfo(String value, Class<T> enumClass) {
def result = convert(value, enumClass)
return [
success: result != null,
value: result,
input: value,
validValues: enumClass.values()*.name()
]
}
// Batch conversion
static <T extends Enum<T>> List<T> convertAll(List<String> values, Class<T> enumClass) {
values.collect { convert(it, enumClass) }.findAll { it != null }
}
}
// Define some enums to test with
enum Color { RED, GREEN, BLUE, YELLOW }
enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE }
enum Status { ACTIVE, INACTIVE, PENDING }
// Use the generic converter
println "Generic conversion:"
println " Color: ${EnumConverter.convert('red', Color)}"
println " Size: ${EnumConverter.convert('LARGE', Size)}"
println " Status: ${EnumConverter.convert('pending', Status)}"
println " Invalid: ${EnumConverter.convert('unknown', Color)}"
println ""
// With defaults
println "With defaults:"
println " Color: ${EnumConverter.convert('invalid', Color, Color.RED)}"
println " Size: ${EnumConverter.convert(null, Size, Size.MEDIUM)}"
println ""
// With info
def info = EnumConverter.convertWithInfo("purple", Color)
println "Conversion info:"
println " Success: ${info.success}"
println " Input: '${info.input}'"
println " Valid values: ${info.validValues}"
println ""
// Batch conversion
def colorInputs = ["red", "BLUE", "purple", "GREEN", "orange", "yellow"]
def colors = EnumConverter.convertAll(colorInputs, Color)
println "Batch conversion: ${colorInputs} -> ${colors}"
println "Invalid count: ${colorInputs.size() - colors.size()}"
Output
Generic conversion: Color: RED Size: LARGE Status: PENDING Invalid: null With defaults: Color: RED Size: MEDIUM Conversion info: Success: false Input: 'purple' Valid values: [RED, GREEN, BLUE, YELLOW] Batch conversion: [red, BLUE, purple, GREEN, orange, yellow] -> [RED, BLUE, GREEN, YELLOW] Invalid count: 2
What happened here: The EnumConverter utility class works with any enum type thanks to generics. The <T extends Enum<T>> type parameter ensures type safety while keeping the code reusable. The convertWithInfo method returns a map with the result, the input, and the list of valid values – perfect for API error responses. The convertAll method handles batch conversion with automatic filtering of invalid values.
Example 12: Real-World Enum Conversion Patterns
What we’re doing: Practical scenarios combining enum conversion with configuration parsing, API responses, and data processing.
Example 12: Real-World Patterns
// Pattern 1: Parse configuration properties
enum LogLevel {
TRACE, DEBUG, INFO, WARN, ERROR, FATAL
static LogLevel fromConfig(String value) {
if (!value?.trim()) return INFO // Default
try { valueOf(value.trim().toUpperCase()) } catch (e) { INFO }
}
}
def config = [
"app.log.level": "debug",
"db.log.level": "WARN",
"cache.log.level": "invalid",
"api.log.level": null
]
println "Log level configuration:"
config.each { key, value ->
def level = LogLevel.fromConfig(value)
println " ${key} = '${value}' -> ${level}"
}
println ""
// Pattern 2: Database row mapping
enum AccountType {
CHECKING("C"), SAVINGS("S"), CREDIT("CR"), INVESTMENT("I")
final String dbCode
AccountType(String dbCode) { this.dbCode = dbCode }
private static final Map<String, AccountType> BY_DB_CODE =
values().collectEntries { [it.dbCode, it] }
static AccountType fromDbCode(String code) {
BY_DB_CODE[code?.toUpperCase()]
}
}
// Simulate database rows
def dbRows = [
[id: 1, name: "Main Checking", type_code: "C", balance: 5000],
[id: 2, name: "Rainy Day Fund", type_code: "S", balance: 15000],
[id: 3, name: "Visa Card", type_code: "CR", balance: -2500],
[id: 4, name: "401k", type_code: "I", balance: 125000]
]
println "Account mapping from DB:"
dbRows.each { row ->
def type = AccountType.fromDbCode(row.type_code)
println " ${row.name.padRight(18)} type=${type?.name()?.padRight(12)} balance=\$${row.balance}"
}
println ""
// Pattern 3: CSV import with error handling
enum Gender {
MALE, FEMALE, NON_BINARY, PREFER_NOT_TO_SAY
private static final Map<String, Gender> ALIASES = [
"M": MALE, "F": FEMALE, "MALE": MALE, "FEMALE": FEMALE,
"NB": NON_BINARY, "NON-BINARY": NON_BINARY, "NONBINARY": NON_BINARY,
"X": PREFER_NOT_TO_SAY, "OTHER": PREFER_NOT_TO_SAY,
"PREFER NOT TO SAY": PREFER_NOT_TO_SAY, "N/A": PREFER_NOT_TO_SAY
]
static Gender fromCsv(String value) {
if (!value?.trim()) return null
ALIASES[value.trim().toUpperCase()]
}
}
def csvRows = ["M", "Female", "NB", "other", "male", "non-binary", "X", "invalid"]
println "CSV gender import:"
csvRows.each { value ->
def gender = Gender.fromCsv(value)
println " '${value}' -> ${gender ?: 'PARSE ERROR'}"
}
println ""
// Pattern 4: Mapping between enum systems
enum ExternalStatus {
ACTIVE, SUSPENDED, CLOSED
}
enum InternalStatus {
ENABLED, DISABLED, ARCHIVED
static InternalStatus fromExternal(ExternalStatus ext) {
switch (ext) {
case ExternalStatus.ACTIVE: return ENABLED
case ExternalStatus.SUSPENDED: return DISABLED
case ExternalStatus.CLOSED: return ARCHIVED
default: return DISABLED
}
}
}
println "Enum mapping:"
ExternalStatus.values().each { ext ->
def internal = InternalStatus.fromExternal(ext)
println " External ${ext} -> Internal ${internal}"
}
Output
Log level configuration: app.log.level = 'debug' -> DEBUG db.log.level = 'WARN' -> WARN cache.log.level = 'invalid' -> INFO api.log.level = 'null' -> INFO Account mapping from DB: Main Checking type=CHECKING balance=$5000 Rainy Day Fund type=SAVINGS balance=$15000 Visa Card type=CREDIT balance=$-2500 401k type=INVESTMENT balance=$125000 CSV gender import: 'M' -> MALE 'Female' -> FEMALE 'NB' -> NON_BINARY 'other' -> PREFER_NOT_TO_SAY 'male' -> MALE 'non-binary' -> NON_BINARY 'X' -> PREFER_NOT_TO_SAY 'invalid' -> PARSE ERROR Enum mapping: External ACTIVE -> Internal ENABLED External SUSPENDED -> Internal DISABLED External CLOSED -> Internal ARCHIVED
What happened here: Four real-world patterns. Config parsing converts property values to log levels with sensible defaults. Database mapping converts short codes stored in DB columns to rich enum objects. CSV import handles the wide variety of input formats users provide, using an alias map. Enum mapping bridges between external and internal systems where the enum names differ. These patterns show up constantly in production applications.
Building a Reliable Conversion Utility
Here’s a complete utility class you can use in your projects:
Reusable Enum Converter Utility
class EnumUtils {
static <T extends Enum<T>> T valueOf(String name, Class<T> enumClass) {
if (!name?.trim()) return null
try {
Enum.valueOf(enumClass, name.trim().toUpperCase())
} catch (IllegalArgumentException e) {
null
}
}
static <T extends Enum<T>> T valueOfOrDefault(String name, Class<T> enumClass, T defaultValue) {
valueOf(name, enumClass) ?: defaultValue
}
static <T extends Enum<T>> boolean isValid(String name, Class<T> enumClass) {
valueOf(name, enumClass) != null
}
static <T extends Enum<T>> List<String> validNames(Class<T> enumClass) {
enumClass.values()*.name()
}
static <T extends Enum<T>> List<T> parseAll(List<String> names, Class<T> enumClass) {
names.collect { valueOf(it, enumClass) }.findAll { it != null }
}
}
// Quick test
enum Fruit { APPLE, BANANA, CHERRY, DATE }
println "valueOf: ${EnumUtils.valueOf('banana', Fruit)}"
println "default: ${EnumUtils.valueOfOrDefault('mango', Fruit, Fruit.APPLE)}"
println "valid: ${EnumUtils.isValid('cherry', Fruit)}"
println "names: ${EnumUtils.validNames(Fruit)}"
println "parseAll: ${EnumUtils.parseAll(['apple', 'invalid', 'date'], Fruit)}"
Output
valueOf: BANANA default: APPLE valid: true names: [APPLE, BANANA, CHERRY, DATE] parseAll: [APPLE, DATE]
Enum Conversion with JSON and APIs
When working with JSON data from APIs, enum conversion becomes critical. Here’s how to handle it cleanly:
JSON Enum Handling
import groovy.json.JsonSlurper
import groovy.json.JsonOutput
enum OrderStatus {
PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED
static OrderStatus fromJson(String value) {
if (!value?.trim()) return null
try { valueOf(value.trim().toUpperCase()) } catch (e) { null }
}
}
// Parse JSON with enum conversion
def json = '''[
{"id": 1, "product": "Widget", "status": "pending"},
{"id": 2, "product": "Gadget", "status": "SHIPPED"},
{"id": 3, "product": "Doohickey", "status": "delivered"},
{"id": 4, "product": "Thingamajig", "status": "UNKNOWN"}
]'''
def orders = new JsonSlurper().parseText(json).collect { raw ->
[
id: raw.id,
product: raw.product,
status: OrderStatus.fromJson(raw.status)
]
}
println "Parsed orders:"
orders.each { order ->
def statusStr = order.status?.name() ?: "PARSE_ERROR"
println " #${order.id} ${order.product.padRight(14)} status=${statusStr}"
}
println ""
// Serialize back to JSON
def outputOrders = orders.findAll { it.status != null }
.collect { [id: it.id, product: it.product, status: it.status.name()] }
def jsonOutput = JsonOutput.prettyPrint(JsonOutput.toJson(outputOrders))
println "Serialized JSON:"
println jsonOutput
Output
Parsed orders:
#1 Widget status=PENDING
#2 Gadget status=SHIPPED
#3 Doohickey status=DELIVERED
#4 Thingamajig status=PARSE_ERROR
Serialized JSON:
[
{
"id": 1,
"product": "Widget",
"status": "PENDING"
},
{
"id": 2,
"product": "Gadget",
"status": "SHIPPED"
},
{
"id": 3,
"product": "Doohickey",
"status": "DELIVERED"
}
]
Performance Considerations
Enum conversion performance matters when processing thousands of records. Here’s what to know:
valueOf()is fast – Java caches a map internally, so it’s O(1) lookup- Pre-built lookup maps are fastest – build a
Map<String, MyEnum>once and reuse it values().find{}is O(n) – it iterates through all constants each time. Fine for small enums, avoid for tight loops with large enums- Try-catch is not expensive – catching
IllegalArgumentExceptionis fast for occasional misses. Only avoid it if the majority of inputs are invalid - String normalization has cost –
trim().toUpperCase()creates new String objects. For millions of conversions, consider caching normalized values
Best Practices
DO:
- Always handle case-insensitivity – user input, config files, and API data can be any case
- Provide a
fromString()orparse()factory method on your enum for clean conversion - Use pre-built lookup maps for O(1) access when you have many conversions or large enums
- Return
nullor a default value for invalid inputs – don’t force callers to catch exceptions - Trim whitespace before conversion – it’s the most common silent failure cause
- Include valid values in error messages to help users correct their input
DON’T:
- Use
valueOf()directly on user input without wrapping it – it will throw on invalid values - Depend on
ordinal()for conversion – it breaks when you reorder or add constants - Swallow exceptions silently without logging – at least log unknown values at debug level
- Create separate conversion logic in every class that uses the enum – centralize it in the enum itself
- Assume API consumers will send exact enum constant names – always normalize input
Conclusion
We covered every way to convert a String to an Enum in Groovy – from the basic valueOf() and as keyword to case-insensitive lookups, alias maps, reverse lookups by property, generic converters, and real-world patterns for JSON, databases, and CSV imports.
The core lesson: never use valueOf() directly on untrusted input. Always wrap it with normalization (trim, uppercase) and error handling (try-catch with a default value). Pre-built lookup maps give you O(1) performance and the flexibility to support aliases and display names. Keep your conversion logic inside the enum itself as a static factory method so it’s easy to find and maintain.
For the complete guide to enums themselves, see our Groovy Enum guide. For other type conversion patterns, check our String to Integer guide.
Summary
valueOf()andasare case-sensitive – always normalize input first- Create a static
fromString()method on your enum for safe, case-insensitive conversion - Use pre-built
Map<String, MyEnum>for O(1) lookup performance - Support aliases and display names for user-friendly input handling
- Return null or a default value instead of throwing exceptions for invalid input
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 Metaprogramming – Runtime Guide
Frequently Asked Questions
Is valueOf() case-sensitive in Groovy enums?
Yes, valueOf() is case-sensitive. Calling Color.valueOf('red') will throw IllegalArgumentException because the constant is ‘RED’. Always convert to uppercase with toUpperCase() before calling valueOf(), or create a fromString() factory method that handles case normalization.
How do I convert a string to an enum without throwing an exception?
Wrap valueOf() in a try-catch and return null or a default value on failure. Create a static fromString() method: static MyEnum fromString(String s) { try { valueOf(s?.trim()?.toUpperCase()) } catch (e) { null } }. This handles null, whitespace, case differences, and invalid values.
What is the difference between valueOf() and the ‘as’ keyword for enums?
They behave identically. ‘BLUE’ as Color calls Color.valueOf('BLUE') internally. Both are case-sensitive and throw IllegalArgumentException for invalid values. The ‘as’ keyword is more Groovy-idiomatic and reads better in collection operations like collect { it as Color }.
How do I look up an enum by a property value instead of its name?
Create a static lookup map using collectEntries: private static final Map BY_CODE = values().collectEntries { [it.code, it] }. Then add a static method: static MyEnum fromCode(int code) { BY_CODE[code] }. This gives O(1) reverse lookup by any property.
Can I convert a string to an enum case-insensitively in Groovy?
Yes, by normalizing the input before conversion. The simplest approach: try { valueOf(input.trim().toUpperCase()) } catch (e) { null }. For better performance, build a pre-computed Map with all names uppercased: values().collectEntries { [it.name().toUpperCase(), it] } and look up in that map.
Related Posts
Previous in Series: Groovy Enum – Constants, Methods, and Best Practices
Next in Series: Groovy Metaprogramming – Runtime Guide
Related Topics You Might Like:
- Groovy Enum – Constants, Methods, and Best Practices
- Groovy String to Integer – Complete Conversion Guide
- Groovy String Tutorial – The Complete Guide
This post is part of the Groovy & Grails Cookbook series on TechnoScripts.com

No comment