Enum in Java: A Complete Guide with Examples
What is an enum in Java
Say you store a coffee size in a string: String size = "BIGG";. The typo compiles, and you only find the bug when a customer doesn't get their coffee. If the size is an enum, the same typo, CoffeeSize.BIGG, is a compile error.
An enum in Java is a special reference type (a kind of class) with a fixed set of instances that are known in advance. These instances are the named enum constants. The word enum is short for enumeration. Enums were added in Java 5 and are declared with the enum keyword.
An enum lists every possible value of something. Suppose your coffee shop sells exactly three sizes: BIG, HUGE and OVERWHELMING. If you use a String for the size, nothing stops a caller from passing MIDDLE, SMALL or any other text. An enum limits the choice to the declared values:
public enum CoffeeSize {
BIG, HUGE, OVERWHELMING
} In its simplest form, an enum is a list of named constants. Each constant (BIG, HUGE, OVERWHELMING) is an object of the enum type that declares it. The constants are implicitly public static final, so you can't reassign them, and each one has exactly one instance.
What data type is an enum?
Every enum is its own reference type. When you declare enum CoffeeSize, you create a new type called CoffeeSize. A variable of that type holds a reference to one of its constants. The JVM creates the constant objects once, when the enum class is initialized, so you never use new with an enum (it won't compile). Unlike a primitive variable, an enum variable can be null:
public class CoffeeSizeExample1 {
public static void main(String[] args) {
CoffeeSize coffeeSize = CoffeeSize.BIG;
if (coffeeSize == CoffeeSize.BIG) {
System.out.println(coffeeSize); // BIG
}
CoffeeSize notChosen = null; // allowed: enum is a reference type
}
} Enum vs String constants
Before Java 5, developers imitated enums with a class full of constants:
public class CoffeeSizeConstants {
public static final String BIG = "BIG";
public static final String HUGE = "HUGE";
public static final String OVERWHELMING = "OVERWHELMING";
} That works, but an enum does much more:
| Feature | String/int constants | enum |
|---|---|---|
| Type safety | No: a method accepts any string | Yes: only constants of this enum can be passed |
| Iterating over all values | By hand | The values() method |
| Fields, constructors, methods | No | Yes |
| Use in switch | Yes, but no completeness check | Yes, and the compiler checks that a switch expression covers every constant |
| Specialized collections | No | EnumSet, EnumMap |
Java enum in a switch statement
You can use enum values in a switch statement. Every case label must be a constant of the same enum as the switch expression. You write the constant name without the type name, because the type of the switch expression already tells the compiler which enum the labels belong to.
public class CoffeeSizeExample2 {
public static void main(String[] args) {
CoffeeSize coffeeSize = CoffeeSize.BIG;
switch (coffeeSize) {
case BIG:
System.out.println("A big cup of coffee, please!");
break;
case HUGE:
System.out.println("A huge cup of coffee, please!");
break;
case OVERWHELMING:
System.out.println("An overwhelming cup of coffee, please!");
break;
default:
System.out.println("No cup selected.");
}
}
} Since Java 14, the arrow-style switch expression is more convenient. If every constant has a case, you don't need a default branch. The compiler checks that the switch is exhaustive, so if someone adds a new constant to the enum and forgets to update the switch, the code won't compile.
String message = switch (coffeeSize) {
case BIG -> "Big cup";
case HUGE -> "Huge cup";
case OVERWHELMING -> "Overwhelming cup";
}; Gotcha
If the enum variable is null, a classic switch throws NullPointerException, and a default branch won't prevent it. Before Java 21, a case label could only use the simple constant name (case BIG), and case CoffeeSize.BIG did not compile. Since Java 21, the qualified name is allowed as well.
The java.lang.Enum class and enum methods
Every enum implicitly extends the java.lang.Enum class. The compiler adds extends Enum<CoffeeSize> for you, which is why you can't write your own extends clause on an enum: Java has no multiple inheritance of classes. A regular class can't extend an enum either, because an enum is implicitly final. The only exception is constants with their own body (see overriding methods in enum constants). For those, the compiler generates anonymous subclasses, and the enum becomes implicitly sealed (Java 17+).
Enum implements Comparable and Serializable, so every enum can be compared and serialized. Enum defines name(), ordinal(), compareTo(), equals(), toString() and other methods. The static methods values() and valueOf(String) aren't declared in Enum. The compiler generates them in every enum automatically.
| Method | Returns | Example for CoffeeSize.HUGE |
|---|---|---|
values() | An array of all constants in declaration order | [BIG, HUGE, OVERWHELMING] |
valueOf(String) | The constant with exactly this name | valueOf("HUGE") → HUGE |
name() | The constant name as written in code | "HUGE" |
toString() | Same as name() by default, can be overridden | "HUGE" |
ordinal() | The zero-based position | 1 |
compareTo(e) | The difference between ordinals | HUGE.compareTo(BIG) → 1 |
equals(o) | true only for the very same constant | HUGE.equals(CoffeeSize.HUGE) → true |
The values() method
values() returns an array with all the enum constants in the order they are declared. This is the standard way to loop over a Java enum:
public class CoffeeSizeValuesExample {
public static void main(String[] args) {
System.out.println("CoffeeSize constants:");
CoffeeSize[] coffeeSizes = CoffeeSize.values();
for (CoffeeSize c : coffeeSizes) {
System.out.println(c);
}
}
} Output:
CoffeeSize constants:
BIG
HUGE
OVERWHELMING Each call to values() returns a new copy of the array, so changing that array doesn't affect the enum. In a hot loop, store the result in a variable instead of calling values() on every iteration.
The valueOf() method: converting a String to an enum
The static valueOf() method returns the constant whose name matches the string argument. It's the built-in way to convert a String to an enum:
public class CoffeeSizeExample3 {
public static void main(String[] args) {
CoffeeSize coffeeSize = CoffeeSize.valueOf("BIG");
System.out.println("coffeeSize is " + coffeeSize);
CoffeeSize wrong = CoffeeSize.valueOf("big"); // IllegalArgumentException
}
} The name must match exactly. The comparison is case-sensitive and whitespace isn't trimmed. If no constant has that name, valueOf() throws IllegalArgumentException. If you pass null, it throws NullPointerException.
name() vs toString()
name() is final and always returns the constant name exactly as written in the source code. toString() returns the same thing by default, but you can override it, for example to show a user-friendly label. When you convert an enum to a string and back again with valueOf(), use name(). It can't be overridden, so the round trip always works.
The ordinal() method
ordinal() returns the position of a constant in the enum declaration, starting from zero:
public class CoffeeSizeExample4 {
public static void main(String[] args) {
for (CoffeeSize c : CoffeeSize.values()) {
System.out.println(c + " " + c.ordinal());
}
}
} BIG 0
HUGE 1
OVERWHELMING 2 The compareTo() method
int compareTo(E e) compares the ordinals of two constants of the same enum. Comparing constants of different enums is a compile error.
- If the calling constant's ordinal is less than that of
e, the result is negative. - If both are the same constant, the result is zero.
- If the calling constant's ordinal is greater, the result is positive.
public class CoffeeSizeCompareTo {
public static void main(String[] args) {
CoffeeSize bigCup = CoffeeSize.BIG;
CoffeeSize hugeCup = CoffeeSize.HUGE;
CoffeeSize anotherBigCup = CoffeeSize.BIG;
CoffeeSize overwhelmingCup = CoffeeSize.OVERWHELMING;
System.out.println("bigCup.compareTo(hugeCup): " + bigCup.compareTo(hugeCup));
System.out.println("hugeCup.compareTo(bigCup): " + hugeCup.compareTo(bigCup));
System.out.println("bigCup.compareTo(anotherBigCup): " + bigCup.compareTo(anotherBigCup));
System.out.println("bigCup.compareTo(overwhelmingCup): " + bigCup.compareTo(overwhelmingCup));
}
} Output:
bigCup.compareTo(hugeCup): -1
hugeCup.compareTo(bigCup): 1
bigCup.compareTo(anotherBigCup): 0
bigCup.compareTo(overwhelmingCup): -2 The current implementation returns this.ordinal() - e.ordinal(), which is why you see -2. The method is final, so the natural order of enum constants always matches their declaration order.
equals() vs == for enums
The equals() method in Enum overrides the one from Object and is declared final. Two constants are equal only if they are the same constant of the same enum. Constants of different enums are never equal, even if their ordinals match.
Each constant exists as a single instance, so you can compare enums with ==. The result is the same as with equals().
public class CoffeeSizeEquals {
public static void main(String[] args) {
CoffeeSize bigCup = CoffeeSize.BIG;
CoffeeSize hugeCup = CoffeeSize.HUGE;
CoffeeSize anotherBigCup = CoffeeSize.BIG;
System.out.println("bigCup.equals(hugeCup): " + bigCup.equals(hugeCup));
System.out.println("bigCup.equals(anotherBigCup): " + bigCup.equals(anotherBigCup));
System.out.println("bigCup == anotherBigCup: " + (bigCup == anotherBigCup));
}
} bigCup.equals(hugeCup): false
bigCup.equals(anotherBigCup): true
bigCup == anotherBigCup: true Tip
For enums, == is actually better than equals(). It can't throw NullPointerException when the left side is null, and the compiler rejects a == comparison between constants of two different enums right away.
Enum with values: fields, constructor and methods
You can't create an enum instance with new, but otherwise an enum works like a regular class. You can add constructors, fields and methods to it, which gives you an enum with values attached to each constant.
Say you want to store the cup volume in milliliters. Add an ml field, a getMl() getter, and a constructor that takes the volume.
An enum constructor is always private. If you omit the modifier, private is implied, and declaring the constructor public or protected is a compile error. So how is the constructor called? You put arguments in parentheses after a constant's name, and that constant is created through the matching constructor. The list of constants must come first in the enum body and must end with a semicolon if other members follow it.
public enum CoffeeSize2 {
// 100, 150 and 200 are passed to the constructor
BIG(100), HUGE(150), OVERWHELMING(200);
private final int ml;
CoffeeSize2(int ml) {
this.ml = ml;
}
public int getMl() {
return ml;
}
} You call enum methods the same way as methods on any other object. This class loops over all the constants and calls getMl() on each one:
public class CoffeeSizeMlExample {
public static void main(String[] args) {
for (CoffeeSize2 coffeeSize : CoffeeSize2.values()) {
System.out.println(coffeeSize + " " + coffeeSize.getMl());
}
}
} BIG 100
HUGE 150
OVERWHELMING 200 Enum constructors can be overloaded. To use the no-argument constructor, leave out the parentheses after the constant:
public enum CoffeeSize3 {
BIG(100), HUGE, OVERWHELMING(200);
private final int ml;
CoffeeSize3(int ml) {
this.ml = ml;
}
CoffeeSize3() {
this.ml = -1;
}
public int getMl() {
return ml;
}
} Where you can declare an enum
You can declare an enum as a top-level type in its own file or as a member of a class. Since Java 16 (JEP 395), you can also declare a local enum inside a method, constructor or block. Nested and local enums are implicitly static, so they can't access instance fields of the enclosing class.
Here the CoffeeSize enum is declared inside the Coffee3 class:
public class Coffee3 {
enum CoffeeSize { BIG, HUGE, OVERWHELMING }
CoffeeSize size;
} To use a nested enum from another class, qualify it with the enclosing class name:
public class CoffeeTest2 {
public static void main(String[] args) {
Coffee3 drink = new Coffee3();
drink.size = Coffee3.CoffeeSize.BIG; // enclosing class name is required
}
} Declaring an enum inside a method (Java 16+):
public class CoffeeTest3 {
public static void main(String[] args) {
enum CoffeeSize { BIG, HUGE, OVERWHELMING }
CoffeeSize coffeeSize = CoffeeSize.BIG;
}
} Overriding methods in enum constants
You can override methods in an enum, but it works a little differently from regular overriding. The method isn't overridden in a named subclass. It's overridden in the body of a specific constant.
Let's add a getLidCode() method that returns the lid code for a cup. All sizes use code B except OVERWHELMING, which needs code A. After that constant, open curly braces and override the method inside them. To override several methods, put them all in the same braces.
public enum CoffeeSize4 {
BIG(100),
HUGE(150),
OVERWHELMING(200) {
@Override
public String getLidCode() {
return "A";
}
};
private final int ml;
CoffeeSize4(int ml) {
this.ml = ml;
}
public int getMl() {
return ml;
}
public String getLidCode() {
return "B";
}
} public class CoffeeSizeExample7 {
public static void main(String[] args) {
for (CoffeeSize4 coffeeSize : CoffeeSize4.values()) {
System.out.println(coffeeSize + " " + coffeeSize.getLidCode());
}
}
} Output:
BIG B
HUGE B
OVERWHELMING A If every constant needs its own behavior, declare the method abstract. The compiler will then require each constant to implement it:
public enum Operation {
PLUS {
public int apply(int a, int b) { return a + b; }
},
MINUS {
public int apply(int a, int b) { return a - b; }
};
public abstract int apply(int a, int b);
} Easy to miss
A constant with a body is an instance of an anonymous subclass. CoffeeSize4.OVERWHELMING.getClass() therefore returns a class like CoffeeSize4$1, not CoffeeSize4. To get the enum type itself, call getDeclaringClass().
Enums and interfaces
An enum can't explicitly extend another class, but it can implement any number of interfaces. You can implement an interface method once for the whole enum or separately in the body of each constant:
public interface HasPrice {
int priceInCents();
}
public enum Currency implements HasPrice {
PENNY(1), NICKEL(5), DIME(10), QUARTER(25);
private final int value;
Currency(int value) {
this.value = value;
}
@Override
public int priceInCents() {
return value;
}
} public class CurrencyExample {
public static void main(String[] args) {
HasPrice coin = Currency.QUARTER;
System.out.println(coin.priceInCents()); // 25
}
} Standard interfaces work the same way. For example, an enum can implement Runnable, and you can then pass one of its constants straight to the Thread constructor.
EnumSet and EnumMap
The java.util package has two collections built specifically for enums. Because they rely on constant ordinals, they are faster and more compact than HashSet and HashMap:
EnumSetis a set of constants from one enum, stored internally as a bit vector.EnumMapis a map with enum constants as keys, backed by an array indexed byordinal().
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
public class EnumCollectionsExample {
public static void main(String[] args) {
Set<CoffeeSize> large = EnumSet.of(CoffeeSize.HUGE, CoffeeSize.OVERWHELMING);
System.out.println(large.contains(CoffeeSize.BIG)); // false
Map<CoffeeSize, Integer> prices = new EnumMap<>(CoffeeSize.class);
prices.put(CoffeeSize.BIG, 150);
prices.put(CoffeeSize.HUGE, 200);
System.out.println(prices); // {BIG=150, HUGE=200}
}
} Where developers get tripped up with enums
- Storing
ordinal()in a database or file. The ordinal changes if someone inserts a constant in the middle or reorders the list. Storename()or a dedicated code field instead. - Calling
valueOf()on unchecked user input."big"or"BIG "throwsIllegalArgumentException. Normalize the input or handle the exception. - Writing
new CoffeeSize()or apublicconstructor. Neither compiles. - Writing
enum A extends B. An enum already extendsjava.lang.Enum. Use interfaces to share behavior. - Mutable fields in an enum. There is one instance of each constant for the whole application, so a mutable field is effectively global state. Make enum fields
final.
Frequently Asked Questions
Is an enum a class in Java?
Yes. An enum is a special kind of class that implicitly extends java.lang.Enum, and each enum declares its own reference type. It can have fields, constructors, methods and interfaces, but you can't instantiate it with new, and its instances are only the declared constants. An enum variable holds a reference and can be null. Constants have integer positions (ordinal()), but an enum can't be cast to int.
What is the difference between enum and java.util.Enumeration?
They are unrelated despite the similar names. enum is the keyword for declaring an enumerated type. java.util.Enumeration is a legacy interface for iterating over collections such as Vector and Hashtable (methods hasMoreElements() and nextElement()). New code uses Iterator instead.
Why is an enum considered the best way to implement a Singleton?
An enum with a single constant (enum Registry { INSTANCE; }) guarantees exactly one instance. The JVM creates it thread-safely during class initialization, reflection can't call an enum constructor, and deserialization returns the same constant. Joshua Bloch recommends this approach in "Effective Java".
How should I store an enum in a database?
The safest choice is the string from name() or a stable code kept in an enum field. Avoid storing ordinal(). After constants are added or reordered, existing records would point to different values. In JPA, use @Enumerated(EnumType.STRING).
Can one enum extend another enum?
No. Every enum already extends java.lang.Enum, and Java doesn't allow multiple class inheritance, so an enum can't have an extends clause. You can't extend an enum either, because it is implicitly final. The only subclasses are the anonymous bodies of its constants. To share behavior between enums, have them implement a common interface.
Comments