Java enums are much more than named integer constants. Introduced in Java 5, the enum construct gives developers a type-safe way to represent a fixed set of values while also supporting fields, constructors, methods and interfaces.
Used well, enums can replace error-prone strings and numeric constants, centralize behavior that belongs with a fixed set of values, and make APIs easier to understand. This tutorial looks at several practical techniques that make Java enums especially useful in production code.
1. Start with type-safe enum constants
A basic enum defines a fixed set of legal values:
enum Code {
ONE,
TWO,
THREE
}
enum Suit {
HEARTS,
DIAMONDS,
CLUBS,
SPADES
}Unlike a collection of unrelated strings or integers, an enum gives the compiler a real type to check. A method that accepts Suit cannot accidentally receive "hearts", 7 or another unrelated value.
public void playCard(Suit suit) {
System.out.println("Playing a " + suit);
}
playCard(Suit.HEARTS);By convention, enum constants are written in uppercase because they are effectively public static final instances of the enum type.
2. Add behavior directly to an enum
Every Java enum implicitly extends java.lang.Enum. An enum can declare fields, constructors and methods just like other Java types, although an enum cannot extend another class.
This makes it possible to place behavior next to the values it describes:
public enum Greeting {
HELLO,
GOOD_MORNING,
GOOD_EVENING;
public String message(String name) {
return switch (this) {
case HELLO -> "Hello, " + name;
case GOOD_MORNING -> "Good morning, " + name;
case GOOD_EVENING -> "Good evening, " + name;
};
}
}Calling the enum is straightforward:
String message = Greeting.GOOD_MORNING.message("Darcy");
System.out.println(message);Keeping this behavior inside the enum can be cleaner than scattering repeated if statements or switch blocks throughout an application.
3. Convert between enum names and text
Java provides several built-in methods for working with enum names. The name() method returns the exact identifier used to declare the constant, and valueOf() performs the reverse operation.
Suit suit = Suit.valueOf("HEARTS");
if (suit == Suit.HEARTS) {
System.out.println("The text was converted to the HEARTS enum.");
}
System.out.println(suit.name());valueOf() is case-sensitive and throws IllegalArgumentException if the text does not match a declared constant. When parsing user input or external data, validate or normalize the value before calling it.
String input = "hearts";
Suit suit = Suit.valueOf(input.trim().toUpperCase());For external formats such as database values, URLs or configuration files, it is often better to define an explicit stable value instead of depending on name(). That lets the Java constant name change without breaking the external representation.
4. Associate enums with external values
An enum constructor can associate each constant with another value. This is useful when a Java API already exposes integer constants or when an application needs a stable code for each enum value.
The following example maps enum constants to JFileChooser selection modes:
import javax.swing.JFileChooser;
public enum ShowNode {
BOTH(
JFileChooser.FILES_AND_DIRECTORIES,
"Select files and folders"
),
FOLDERS(
JFileChooser.DIRECTORIES_ONLY,
"Select folders"
),
FILES(
JFileChooser.FILES_ONLY,
"Select files"
);
private final int code;
private final String description;
ShowNode(int code, String description) {
this.code = code;
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
}This approach keeps the numeric code and human-readable description with the enum value they belong to. It also avoids a separate switch statement whose only job is to map constants to data.
JFileChooser chooser = new JFileChooser();
ShowNode mode = ShowNode.FOLDERS;
chooser.setFileSelectionMode(mode.getCode());
System.out.println(mode.getDescription());5. Prefer EnumMap when enum values are keys
Every enum constant has an ordinal() value that represents its zero-based declaration position. For example, the first constant has ordinal 0 and the second has ordinal 1.
System.out.println(Suit.HEARTS.ordinal()); // 0
System.out.println(Suit.DIAMONDS.ordinal()); // 1That can make it tempting to store data in an array or list using ordinal() as the index. It works, but it creates a hidden dependency on the declaration order. If someone later reorders the enum constants, the ordinal values change.
For most application code, EnumMap is a safer and clearer alternative. It is specifically optimized for enum keys:
import java.util.EnumMap;
import java.util.Map;
public class ClientSettings {
enum Setting {
NAME,
ADDRESS,
PHONE
}
private final Map<Setting, String> values =
new EnumMap<>(Setting.class);
public ClientSettings() {
values.put(Setting.NAME, "");
values.put(Setting.ADDRESS, "");
values.put(Setting.PHONE, "");
}
public String get(Setting setting) {
return values.get(setting);
}
public void set(Setting setting, String value) {
values.put(setting, value);
}
public static void main(String[] args) {
ClientSettings settings = new ClientSettings();
settings.set(Setting.NAME, "Cameron");
settings.set(Setting.PHONE, "555-0100");
System.out.println(settings.get(Setting.NAME));
System.out.println(settings.get(Setting.PHONE));
}
}EnumMap communicates the intent directly: the keys are enum constants. It also avoids coupling application behavior to declaration order.
When is ordinal-based storage acceptable?
An array indexed by ordinal() can still be reasonable inside tightly controlled, performance-sensitive code where the enum and array are private implementation details and always evolve together. It should not be used as a persistent identifier, database value, serialized contract or public API value.
If an enum needs a stable external number, define that number explicitly:
public enum Status {
NEW(10),
ACTIVE(20),
CLOSED(30);
private final int code;
Status(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}Now the external code remains stable even if the enum constants are reordered.
Java enum best practices
- Use enums when the valid values form a known, finite set.
- Use uppercase names for enum constants.
- Put behavior in the enum when that behavior naturally belongs to the values.
- Use explicit fields for stable database, JSON or configuration values.
- Use
EnumMapandEnumSetwhen enum constants act as map keys or set members. - Avoid persisting or exposing
ordinal()because declaration order can change.
Why Java enums improve maintainability
Enums make a fixed domain of values explicit in the type system. They can eliminate magic strings and numbers, centralize related behavior and let the compiler catch invalid values before code reaches production.
The most effective Java enums are not simply replacements for integer constants. They are small, focused types that combine a controlled set of values with the data and behavior that belong to those values. Used that way, enums can make Java code easier to read, safer to change and simpler to maintain.