Java Enum with Values: Fields, Constructors, and Lookup
java enum with values: Learn how to define Java enums with associated values, add methods, and look up constants by their fields.
Java enums are more than a list of named constants. When you need to attach related data to each constant—like a status code, a label, or a configuration value—you can define a java enum with values by adding fields, a constructor, and accessor methods. This article shows how to model such enums correctly and where they break down.
Defining an Enum with Fields and a Constructor
To give each enum constant its own data, declare private final fields and a constructor that initializes them. The constructor is implicitly private and can only be called within the enum body. Here is a typical example:
public enum OrderStatus { PENDING("PENDING", 1), PROCESSING("PROCESSING", 2), SHIPPED("SHIPPED", 3), DELIVERED("DELIVERED", 4); private final String label; private final int code; OrderStatus(String label, int code) { this.label = label; this.code = code; } public String getLabel() { return label; } public int getCode() { return code; } }
The private final modifiers are important. Once the constant is created, its values should not change. If you need to expose them, provide getters but no setters. This keeps the enum immutable and safe to share across threads.
Adding Behavior with Methods
An enum with values can also contain methods that use those values. This keeps related logic in one place. For example, you might want to know whether a status is terminal:
public enum OrderStatus { // ... constants and fields as above public boolean isTerminal() { return this == DELIVERED || this == CANCELLED; } }
If you include CANCELLED in the constant list, remember to give it a label and code as well. Methods can access the private fields directly because they belong to the same enum type. This pattern works well for small pieces of behavior that are tightly coupled to the constant's meaning.
Using Enum Values in Switch Statements
One of the main advantages of an enum is that you can switch on its constants without using the enum type name in each case. The compiler checks that you cover all constants if you use an exhaustive switch (e.g., with switch expressions in newer Java versions). Here is a traditional switch statement:
public String formatStatus(OrderStatus status) { switch (status) { case PENDING: return "Pending: " + status.getLabel(); case PROCESSING: return "Processing: " + status.getLabel(); case SHIPPED: return "Shipped: " + status.getLabel(); case DELIVERED: return "Delivered: " + status.getLabel(); default: throw new IllegalArgumentException("Unknown status: " + status); } }
Notice that the case labels are the constant names, not the qualified OrderStatus.PENDING. The switch works because the enum type is already known from the parameter. This is a clean way to map each constant to a specific behavior or message.
Looking Up an Enum by Its Value
A common requirement is to convert a code or label back into the enum constant. For example, you might receive an integer from a database or a REST API. A simple approach is to iterate over values():
public static OrderStatus fromCode(int code) { for (OrderStatus status : values()) { if (status.code == code) { return status; } } throw new IllegalArgumentException("Unknown code: " + code); }
This works, but it is O(n) for each lookup. If you perform many lookups in a hot path, consider building a Map<Integer, OrderStatus> once, either in a static initializer or with Stream.toMap. For example:
private static final Map<Integer, OrderStatus> BY_CODE = Arrays.stream(values()) .collect(Collectors.toMap(OrderStatus::getCode, status -> status)); public static OrderStatus fromCode(int code) { OrderStatus status = BY_CODE.get(code); if (status == null) { throw new IllegalArgumentException("Unknown code: " + code); } return status; }
The map is built once when the enum class is loaded, so subsequent lookups are constant time. Make the map unmodifiable if you want to be strict about immutability.
Common Pitfalls and Maintainability
When adding values to an enum, keep the fields final and avoid exposing mutable state. If you need to associate more than a few values, consider whether the enum is still the right abstraction. An enum with dozens of constants and many fields can become hard to read. Also, avoid putting heavy business logic inside the enum. Instead, use the enum to select a strategy or a configuration object.
Another pitfall is relying on ordinal() to store a meaningful value. The ordinal is the position of the constant in the declaration, and it changes if you reorder constants. Always use an explicit field for a stable code or ID.
Performance and Runtime Behavior
Each enum constant is a singleton instance created when the enum class is initialized. The memory footprint is small: one object per constant, plus the fields you define. Accessing a field via a getter is a simple method call, which the JVM can inline. Switch statements on enums are compiled to efficient bytecode (either tableswitch or lookupswitch), so they are fast. If you need to look up by a value frequently, the map approach avoids repeated linear scans. There is no measurable difference for typical application workloads, so choose based on clarity and maintainability rather than micro-optimization.
When to Use Enum with Values vs Alternatives
An enum with values is the right choice when you have a fixed set of constants that are known at compile time and you want type safety. The compiler prevents you from passing an invalid constant, and switch statements can be exhaustive. This is ideal for status codes, configuration keys, or fixed categories.
If the set of values is dynamic—for example, loaded from a database or a configuration file—an enum is not suitable. In that case, use a regular class with a static factory, or a Map to hold the associations. Similarly, if you need to add new values without recompiling the code, an enum forces you to change the source. Weigh the benefit of compile-time safety against the need for runtime extensibility.