Java Enum with Multiple Values: Fields and Reverse Lookup

Create a Java enum with multiple values per constant, read them with getters, and find the constant from any value with a cached map and Optional.

Diagram of the enum constant ACTIVE with the values A, active and 1 going through the constructor into fields, and a static block filling the maps BY_NAME and BY_CODE used by the lookup methods

A Java enum with multiple values is an enum whose constants each carry several fields, such as a short code, a display name and a number, which we pass to the enum constructor and read back through getter methods. We can also find the constant from any of those values with a static lookup method.

We use such an enum when one business state has more than one representation, for example a status that a database stores as 1, a partner API sends as “A” and the UI shows as active. The enum keeps all three in one place, so the mapping code does not spread over the app.

The following example reads the values of the AccountStatus constant ACTIVE and looks up constants by a code and by a name.

AccountStatus status = AccountStatus.ACTIVE;
String shortName = status.getShortName();                              // "A"
String fullName = status.getFullName();                                // "active"
int code = status.getCode();                                           // 1
AccountStatus byCode = AccountStatus.fromCode(0).orElseThrow();        // INACTIVE
AccountStatus byName = AccountStatus.fromValue("P").orElseThrow();     // PURGED
Optional<AccountStatus> unknown = AccountStatus.fromValue("X");        // Optional.empty

Notice that the lookup methods return an Optional, so an unknown value never turns into a null in our code. We first build the enum step by step, add a reverse lookup with a cached map, and handle unknown input. After that, we give each constant a variable number of values and answer the common follow-up questions.

1. How to Create Enum with Multiple Values

An enum constant is an object, so it can hold fields like any other object. The syntax for an enum with several values per constant is the same as for an enum with one value; only the constructor takes more parameters. The basics of constructors and fields in enums are in our Java enum tutorial.

To give every constant several values, we follow four steps.

  1. Declare each constant with its values in parentheses, such as ACTIVE(“A”, “active”, 1).
  2. Write a constructor with one parameter per value and assign each parameter to a private final field.
  3. Add a getter for each field, so the rest of the app can read the values.
  4. Add static lookup methods that return the constant for a given value, backed by maps that a static block fills.
Diagram of the enum constant ACTIVE with the values A, active and 1 going through the constructor into fields, and a static block filling the maps BY_NAME and BY_CODE used by the lookup methods
The constructor stores the values of each constant, and a static block builds the lookup maps once all constants exist

The constructor of an enum is always private, even without the keyword, so no code outside the enum can create a fourth status. The fields are final because enum constants are shared by the whole app, and a value that changes at runtime would affect every caller.

2. Example of Enum with Multiple Values

A banking app stores the status of each account in a database column as a number, receives it from a partner system as a one-letter code, and prints the full word in emails. The enum AccountStatus holds all three values for each status, which are a short name, a full name and a numeric code.

public enum AccountStatus {

    PURGED("P", "purged", -1),
    INACTIVE("I", "inactive", 0),
    ACTIVE("A", "active", 1);

    private static final Map<String, AccountStatus> BY_NAME = new HashMap<>();
    private static final Map<Integer, AccountStatus> BY_CODE = new HashMap<>();

    static {
        for (AccountStatus status : values()) {
            BY_NAME.put(status.shortName.toLowerCase(Locale.ROOT), status);
            BY_NAME.put(status.fullName.toLowerCase(Locale.ROOT), status);
            BY_CODE.put(status.code, status);
        }
    }

    private final String shortName;
    private final String fullName;
    private final int code;

    AccountStatus(String shortName, String fullName, int code) {
        this.shortName = shortName;
        this.fullName = fullName;
        this.code = code;
    }

    public String getShortName() {
        return shortName;
    }

    public String getFullName() {
        return fullName;
    }

    public int getCode() {
        return code;
    }

    public static Optional<AccountStatus> fromValue(String value) {
        if (value == null) {
            return Optional.empty();
        }
        return Optional.ofNullable(BY_NAME.get(value.strip().toLowerCase(Locale.ROOT)));
    }

    public static Optional<AccountStatus> fromCode(int code) {
        return Optional.ofNullable(BY_CODE.get(code));
    }
}

The method values() returns all constants in declaration order, so a loop over it prints every status with its values. A report page or a dropdown in the admin UI is built the same way.

for (AccountStatus as : AccountStatus.values()) {
    System.out.println("Status " + as.getCode() + " is " + as.getFullName() + " (" + as.getShortName() + ")");
}
Status -1 is purged (P)
Status 0 is inactive (I)
Status 1 is active (A)

The constant name ACTIVE and the field fullName are two different things. The method name() returns the identifier from the source code, whereas getFullName() returns the value we chose. Converting between an enum and its name is covered in Java enum to String and String to enum.

String constantName = AccountStatus.ACTIVE.name();          // "ACTIVE"
String displayName = AccountStatus.ACTIVE.getFullName();     // "active"
int position = AccountStatus.ACTIVE.ordinal();               // 2

3. Reverse Lookup from a Value to the Enum Constant

A reverse lookup returns the constant for a value, such as INACTIVE for the code 0. The built-in method valueOf() only accepts the constant name, so for our own values we write the lookup ourselves. There are two common ways to write it.

  • A stream or a loop over values() that compares each constant, which needs no extra fields.
  • A static HashMap that maps each value to its constant, filled once when the enum class is initialized.

The stream version walks through all constants and creates a new stream on every call.

static Optional<AccountStatus> findByCode(int code) {
    return Arrays.stream(AccountStatus.values())
            .filter(status -> status.getCode() == code)
            .findFirst();
}
Optional<AccountStatus> inactive = findByCode(0);      // Optional[INACTIVE]
Optional<AccountStatus> missing = findByCode(7);       // Optional.empty

The map version in AccountStatus does one hash lookup per call. For three constants, the difference does not matter. For an enum with 200 country or currency codes that a payment service converts for every request, the map is the better choice, and it also catches duplicate values, as we will see in section 5.

Lookup styleCost per callExtra codeUse it when
Stream over values()Copies the array and checks every constantNoneThe enum has a few constants and lookups are rare
Static HashMapOne hash lookupA map field and a static blockLookups run for every request, or the enum is large
valueOf(name)One map lookup inside the JDKNoneThe input is the constant name itself

3.1. Why the Constructor Cannot Fill the Map

Filling the map from the constructor of each constant looks natural. The compiler rejects that, because enum constants are created before the other static fields of the enum are initialized. At the time ACTIVE runs its constructor, BY_NAME is still null, so the Java Language Specification makes the access a compile-time error.

// does not compile: illegal reference to static field from initializer
enum BrokenStatus {
    ACTIVE("A");

    private static final Map<String, BrokenStatus> BY_NAME = new HashMap<>();

    BrokenStatus(String shortName) {
        BY_NAME.put(shortName, this);
    }
}

We fill the lookup maps in a static block placed after the constants, because the JVM runs it once, after every constant exists. Static field initializers such as new HashMap<>() also run after the constants, in source order, so the maps of AccountStatus exist before its static block fills them.

4. Handling Unknown and Messy Input

Values from outside the app are not always clean. A CSV import may contain ” Active “ with spaces and mixed case, an old record may contain a code that was removed years ago, and a JSON field may be missing. The method fromValue() strips the input and compares it in lowercase with Locale.ROOT, so the lookup does not depend on the server’s language settings.

AccountStatus trimmed = AccountStatus.fromValue(" Active ").orElseThrow();                  // ACTIVE
AccountStatus fallback = AccountStatus.fromValue("closed").orElse(AccountStatus.INACTIVE);     // INACTIVE
Optional<AccountStatus> missing = AccountStatus.fromValue(null);                               // Optional.empty
AccountStatus strict = AccountStatus.fromValue("closed").orElseThrow(() -> new IllegalArgumentException("Unknown status"));   // IllegalArgumentException: Unknown status

The caller decides what an unknown value means. A batch import typically logs the row and skips it, so it uses orElse() or isEmpty(), whereas a REST endpoint rejects the request with a clear message, so it uses orElseThrow(). The Optional return type makes that choice visible at every call site.

For comparison, valueOf() throws for any string that is not a constant name, including the lowercase full name.

AccountStatus byConstantName = AccountStatus.valueOf("ACTIVE");    // ACTIVE
AccountStatus wrongCase = AccountStatus.valueOf("active");          // IllegalArgumentException: No enum constant AccountStatus.active

5. Enum with a Variable Number of Values

Sometimes the number of values differs from constant to constant. A checkout service, for example, receives the payment type from several frontends, and each one spells it differently, such as card, credit or debit. A varargs constructor accepts any number of aliases, and the enum stores them in an unmodifiable list.

public enum PaymentMethod {

    CARD("card", "credit", "debit"),
    BANK_TRANSFER("bank", "transfer"),
    CASH("cash");

    private static final Map<String, PaymentMethod> BY_ALIAS = new HashMap<>();

    static {
        for (PaymentMethod method : values()) {
            for (String alias : method.aliases) {
                PaymentMethod previous = BY_ALIAS.putIfAbsent(alias, method);
                if (previous != null) {
                    throw new IllegalStateException("Alias " + alias + " is used by " + previous + " and " + method);
                }
            }
        }
    }

    private final List<String> aliases;

    PaymentMethod(String... aliases) {
        this.aliases = List.of(aliases);
    }

    public List<String> getAliases() {
        return aliases;
    }

    public static Optional<PaymentMethod> fromAlias(String alias) {
        return alias == null ? Optional.empty() : Optional.ofNullable(BY_ALIAS.get(alias.strip().toLowerCase(Locale.ROOT)));
    }
}
List<String> cardAliases = PaymentMethod.CARD.getAliases();               // [card, credit, debit]
PaymentMethod debit = PaymentMethod.fromAlias("Debit").orElseThrow();        // CARD
PaymentMethod transfer = PaymentMethod.fromAlias("transfer").orElseThrow();  // BANK_TRANSFER

The method putIfAbsent() returns the constant that already owns an alias. If a developer adds “cash” to CARD by mistake, the static block throws an IllegalStateException, and the JVM wraps it in an ExceptionInInitializerError the first time any code touches PaymentMethod. The app fails at startup or in the first test instead of sending payments to the wrong type.

A plain put() would replace the first constant without any message, and the lookup would return the last constant declared with that value. The same check works for AccountStatus, where two statuses with the same code would be a bug.

When the values of one constant belong together and are passed around as a unit, a record field is a third option. The enum stores one record per constant instead of several separate fields.

DesignExampleGood for
One field per valueACTIVE(“A”, “active”, 1)A fixed set of values with different types
Varargs listCARD(“card”, “credit”, “debit”)A different number of values of the same type
Record fieldACTIVE(new StatusLabel(“A”, “active”))Values that other code also uses as one object
External EnumMapEnumMap<AccountStatus, String> of colorsValues that belong to one screen or module, not to the enum

6. Java Enum with Multiple Values FAQs

Enums with several values raise new questions once they reach a database table or a JSON payload.

6.1. Can an Enum Constructor Be Public?

No. An enum constructor is always private, and adding public or protected is a compile-time error, so the only instances are the declared constants. Leaving out the modifier is the common style, because the constructor is private either way.

// does not compile: modifier public not allowed here
enum Size {
    SMALL;

    public Size() {
    }
}

6.2. Can an Enum Have More Than One Constructor?

Yes. An enum can overload its constructor like a class, and one constructor can call another with this(…). It lets some constants skip a value that has a default.

public enum Priority {

    LOW(1),
    HIGH(10, "Urgent");

    private final int weight;
    private final String label;

    Priority(int weight) {
        this(weight, "Normal");
    }

    Priority(int weight, String label) {
        this.weight = weight;
        this.label = label;
    }

    public String getLabel() {
        return label;
    }
}
String lowLabel = Priority.LOW.getLabel();      // "Normal"
String highLabel = Priority.HIGH.getLabel();    // "Urgent"

6.3. How Do We Store Such an Enum in a Database or JSON?

We store one of its values, such as the numeric code or the short name, never the ordinal(), because the ordinal changes when someone adds a constant in the middle. With Jakarta Persistence, an AttributeConverter<AccountStatus, Integer> calls getCode() when it writes and fromCode() when it reads. With Jackson, @JsonValue on getShortName() writes the short name, and @JsonCreator on a static factory such as fromValue() reads it back.

6.4. Is the Static Lookup Map Thread-Safe?

Yes, as long as nothing changes it after the static block. The JVM runs a class’s static initialization once and makes its result visible to every thread that uses the class, so concurrent reads of BY_NAME are safe. To rule out later changes, we can wrap the filled map with Map.copyOf() and store the unmodifiable copy.

6.5. Can Two Constants Share the Same Value?

Yes, the compiler allows it, but a map lookup can return only one of them. With put(), the constant declared last wins without any warning. If the shared value is a mistake, the putIfAbsent() check from section 5 makes the enum fail at class initialization. If the sharing is on purpose, the lookup must return a List or Set of constants instead of one.

7. Conclusion

An enum with multiple values gives each constant its own fields through a private constructor, and getters expose them to the rest of the app. A static block after the constants fills one lookup map per kind of value, which is the only place where those maps can be filled, because the constructor runs before the static fields exist.

Lookup methods that strip and lowercase the input and return an Optional handle the messy values that come from files and APIs. For constants with a different number of values, a varargs constructor and a putIfAbsent() check keep the aliases unique, so a duplicate fails at startup and not in production data.

8. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. I want all values from a constant.

    e.g In your example

    if I am asking PURGED then it will return all (“P”, “purged”, -1) these values in a list.

    like List<Object> list = AccountStatus.valueOf(PURGED );

    list = “P”, “purged”, -1

    Is it possible?

  2. Suppose that you have another enum with a different mapping:

    public enum AccountStatusAdmin
    {
    PURGED(“O”, “purged”, -1),
    INACTIVE(“K”, “inactive”, 0),
    ACTIVE(“X”, “active”, 1);

    how to handle both the enum to have as return value only one enum?

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.