Java Enum Design & Generic Utility

1. Problem Background

In many business scenarios, enums need to convert from a String value:

1
StudentStatus.from("enrolled");

Without abstraction, every enum repeats the same logic:

1
2
3
4
5
for (StudentStatus s : StudentStatus.values()) {
if (s.getValue().equals(value)) {
return s;
}
}

This leads to code duplication and poor maintainability. We need to keep code dry

2. Goal

  • Eliminate repeated from() logic

  • Keep type safety

  • Maintain clean and readable API

  • Make the solution reusable across all enums

3. Final Design (Best Practice)

3.1 Base Interface

1
2
3
public interface BaseEnum {
String getValue();
}

Purpose:

  • Defines a contract
  • Ensures all enums expose getValue()

3.2 Generic Utility Class

1
2
3
4
5
6
7
8
9
10
11
public class EnumUtil {

public static <T extends Enum<T> & BaseEnum> T fromValue(Class<T> enumClass, String value) {
for (T constant : enumClass.getEnumConstants()) {
if (constant.getValue().equals(value)) {
return constant;
}
}
throw new RuntimeException("Invalid value: " + value);
}
}

3.3 Enum Implementation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Getter
public enum StudentStatus implements BaseEnum {

ENROLLED("enrolled"),
ABSENT("absent"),
COMPLETED("completed");

private final String value;

StudentStatus(String value) {
this.value = value;
}

public static StudentStatus from(String value) {
return EnumUtil.fromValue(StudentStatus.class, value);
}
}

4. Key Concepts Explained

4.1 Why Use an Interface?

Without BaseEnum:

1
<T extends Enum<T>>

The compiler only knows it’s an enum
It does NOT know getValue() exists

With BaseEnum:

1
<T extends Enum<T> & BaseEnum>

Now the compiler guarantees:

  • getValue() exists

4.2 Lombok vs Interface

Feature Lombok Interface
Generate getter
Enforce method existence

Lombok helps reduce code
Interface ensures type safety

4.3 Understanding Generics

  • A named type parameter

  • Can be used throughout the method

?

  • An anonymous wildcard

  • Cannot be used for operations

4.4 Difference: T vs ?

Feature T ?
Has a name
Can be used as return type
Can be modified
Read-only usage

4.5 Class<T> vs Class<?>

Type Meaning
Class<T> Known specific type
Class<?> Unknown type

Example:

1
2
Class<StudentStatus> clazz = StudentStatus.class; // specific
Class<?> clazz = StudentStatus.class; // generic

5. Why Not Skip the Interface?

Option 1: Use Lombok only

Problem:

  • Compiler cannot confirm getValue()
  • Generic method fails

Option 2: Use name()

1
constant.name().equals(value)

Problems:

  • Not flexible
  • Tightly coupled to enum names
  • Not suitable for real business cases

Option 3: Use Reflection

1
constant.getClass().getMethod("getValue")

Problems:

  • Slow
  • Unsafe
  • Hard to maintain

6. Why Keep from() Inside Enum?

Better readability:

1
StudentStatus.from("enrolled");

vs

1
EnumUtil.fromValue(StudentStatus.class, "enrolled");

Enum version is more expressive and domain-oriented

7. Design Philosophy

Key Idea:

Interfaces are not for writing code — they are for enforcing contracts.

Good Design =

  • Simple usage
  • Strong constraints internally
  • Reusable components

8. Final Takeaways

  • Use interface + generic utility + enum wrapper

  • Lombok is for convenience, not for type safety

  • Generics (T) enable reusable and safe design

  • Avoid shortcuts that sacrifice maintainability