Java Enums: Fields, Methods and Constructors (1Z0-831)
A Java enum is a class whose instances are a fixed set of named constants, declared in the enum itself, and which can carry fields, constructors and methods like any other class. This lesson covers objective 3.7 of the Java SE 25 Developer exam (1Z0-831): how an enum body is laid out, the rules for enum constructors, when constants are constructed and why constructors cannot use the enum's static fields, the built-in methods values(), valueOf(), ordinal(), name() and toString(), comparing and sorting constants, constant-specific bodies, enums that implement interfaces, and how enums behave in switch.
On this page10 sections
- Enum declaration rules: constants first, then members
- What kind of class an enum is
- Enum constructors: private, overloadable and never called with new
- When enum constants are constructed
- Static state inside enum constructors
- Fields and methods: data on each constant
- values(), valueOf(), ordinal(), name() and toString()
- Constant-specific class bodies
- Enums that implement interfaces
- Enums in switch: exhaustiveness and null
- Declare an enum with constants, fields, constructors and methods in the required order, matching each constant's arguments to a constructor
- Apply the enum constructor rules: implicitly private, overloadable, chained with this(...), no super(...) and no new
- Predict when and in what order enum constants are constructed relative to static blocks and static fields
- Explain why an enum constructor cannot use the enum's non-constant static fields, and trace the traps of indirect static access and calling values() during construction
- Use values(), valueOf(), ordinal(), name(), toString(), ==, equals and compareTo correctly
- Write constant-specific class bodies and abstract or interface methods implemented per constant, and explain getClass() versus getDeclaringClass()
- Decide whether a switch over an enum compiles and what it does with a null selector
Enum declaration rules: constants first, then members
An enum body must start with its list of constants; if anything else follows, the list ends with a semicolon and the fields, constructors and methods come after it. A constant written with arguments, such as SLOW(30), passes them to a constructor of the enum, so every argument list must match some declared constructor; a constant with no argument list uses a no-argument constructor, which exists implicitly only when the enum declares no constructor at all.
enum Speed {
SLOW(30), CITY(50), MOTORWAY;
private final int kmh;
Speed() {
this(110);
}
Speed(int kmh) {
this.kmh = kmh;
}
int kmh() {
return kmh;
}
static Speed forLimit(int kmh) {
for (Speed s : values()) {
if (s.kmh == kmh) {
return s;
}
}
return null;
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Speed.CITY.kmh() + " "
+ Speed.forLimit(110) + " " + Speed.forLimit(70));
}
}
This prints 50 MOTORWAY null. MOTORWAY has no arguments, so it calls Speed(), which delegates to Speed(int) with 110. The static method forLimit is ordinary enum code: it loops over values(), can read the private field of each constant because it is inside the enum, and returns null when nothing matches.
The layout rules that break compilation:
- A field or method declared before the constants: the compiler expects a constant there (
enum constant expected here). - Constants with arguments but no matching constructor: if the enum declares no constructor, only the implicit no-argument one exists, so a constant such as
SLOW(30)cannot be applied. - C-style values such as
SLOW = 30: not Java syntax. Data belongs in a field set by a constructor. - A missing semicolon between the last constant and the first member.
The comma after the last constant is optional, and so is the semicolon when the body contains nothing but constants (enum Season { WINTER, SPRING, SUMMER, AUTUMN }).
What kind of class an enum is
An enum is a special class: it implicitly extends java.lang.Enum, which gives it name(), ordinal(), compareTo() and the other built-in methods, and makes every enum Comparable. Because the superclass is fixed, an enum declaration cannot have an extends clause. It can implement any number of interfaces.
No class can extend an enum. An enum whose constants have no bodies is implicitly final, and class Sub extends Mode fails with cannot inherit from final Mode and enum classes are not extensible. When some constants have class bodies the enum is implicitly sealed, permitting only those anonymous bodies. Either way you never write final, sealed or abstract on an enum yourself; those modifiers are rejected.
| Declaration | Compiles? | Why |
|---|---|---|
enum Task implements Runnable { RUN; public void run() { } } | Yes | Enums can implement interfaces |
enum Task extends Thread { RUN } | No | The superclass is always Enum |
class Job extends Mode { } | No | Enums cannot be extended |
enum Task { RUN; abstract void go(); } | No | An abstract method needs a body in every constant |
void demo() { enum Local { X, Y } } | Yes | Local enums are legal since Java 16 |
An enum nested in a class is implicitly static, and an enum can also be declared inside a method body (a local enum). Neither can capture local variables of the method around it.
Enum constructors: private, overloadable and never called with new
Enum constructors are always private: a constructor with no modifier is treated as private, an explicit private is allowed, and public or protected is a compile error (modifier public not allowed here). The reason is that the constants listed in the declaration are the only instances an enum can ever have, so outside code is never allowed to create one; new Level() fails with enum classes may not be instantiated, even inside the enum itself.
Within that restriction, enum constructors behave like ordinary ones. They can be overloaded, and each constant picks the constructor whose parameters match its arguments. They can delegate to each other with this(...), which is the usual way to supply defaults while still assigning every final field exactly once. What they cannot do is call super(...): the compiler generates the call to Enum's constructor (passing the name and ordinal) itself, and an explicit one fails with call to super not allowed in enum constructor.
A constructor body may also do ordinary work such as printing or computing a field from name() or ordinal(). What it may read from the enum's static state is restricted, which the section on static state below explains.
When enum constants are constructed
Every constant is constructed exactly once, in declaration order, when the enum class is initialized, which happens at its first active use, not when the program starts. Each constant is a public static final field of the enum, and those fields are the first static initializers in the class because the constants must be declared first. So all constructors run before any static block or other static field initializer written later in the enum, and the remaining static initializers then run in the order they are written.
enum Stage {
DRAFT, REVIEW, LIVE;
static final StringBuilder LOG = new StringBuilder("log:");
static {
LOG.append(" static");
}
Stage() {
System.out.print("[" + ordinal() + "]");
}
}
public class Main {
public static void main(String[] args) {
System.out.print("begin ");
Stage s = Stage.valueOf("LIVE");
System.out.println(" " + s + " " + Stage.LOG);
}
}
This prints begin [0][1][2] LIVE log: static. Nothing in Stage runs until main first uses the enum; here that use is a valueOf call, but reading any constant or calling any static method has the same effect. Initialization constructs all three constants in declaration order, even though the code only asks for LIVE. Only then is LOG created and the static block appends to it. Later uses of any constant construct nothing new: the instances already exist.
Static state inside enum constructors
An enum constructor cannot refer to the enum's own static fields, unless the field is a constant variable (a static final primitive or String initialized with a compile-time constant). The rule (JLS 8.9.2) follows from the timing above: when a constructor runs, the static fields declared after the constants have not been initialized yet, so the compiler refuses direct references with illegal reference to static field from initializer. The same applies to instance initializers and instance field initializers.
import java.util.ArrayList;
import java.util.List;
enum Seat {
WINDOW, AISLE;
static final String PREFIX = "S-";
private static final List<String> booked = new ArrayList<>();
final String code;
Seat() {
code = PREFIX + name();
booked.add(code);
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Seat.AISLE.code);
}
}
This fails at booked.add(code): booked is a static field, and at that moment it would still be null. The line above it is fine, because PREFIX is a constant variable whose value the compiler inlines; delete the booked line and the program prints S-AISLE. Being static final is not enough on its own: booked is final too, but a List created with new is not a compile-time constant. The enum's constants are static fields as well and are not constant variables, so a constructor cannot name a sibling constant either. The same registry pattern compiles in an ordinary class, which is what makes the rule easy to forget.
The check only covers direct references written in the constructor. Calling a static method that touches a static field compiles, but the method runs before that field's own initializer:
import java.util.ArrayList;
import java.util.List;
enum Region {
NORTH, SOUTH;
private static List<String> names = new ArrayList<>();
Region() {
remember(name());
}
private static void remember(String n) {
if (names == null) {
names = new ArrayList<>();
}
names.add(n);
}
static List<String> names() {
return names;
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Region.names());
}
}
This prints []. Each constructor calls remember, which finds names still null, creates a list and adds the constant's name. After both constants exist, the field's own initializer names = new ArrayList<>() runs and replaces that list with an empty one. Declared without an initializer, the field keeps the list the constructors built and the program prints [NORTH, SOUTH].
The hidden array behind values() is assigned only after all constants exist, so calling values() while a constant is being constructed throws:
enum Tier {
BRONZE, GOLD;
final String tag;
Tier() {
tag = name() + "/" + values().length;
}
}
public class Main {
public static void main(String[] args) {
try {
System.out.println(Tier.GOLD.tag);
} catch (ExceptionInInitializerError e) {
System.out.println(e.getCause()
instanceof NullPointerException);
}
}
}
This prints true. The first constructor's call to values() throws a NullPointerException during the enum's static initialization, so the JVM wraps it in ExceptionInInitializerError, and the handler confirms the cause. Code that needs all constants, such as a lookup map, belongs in a static block after the constants or in a method called later.
Fields and methods: data on each constant
Enum fields give each constant its own data, and enum methods work on that data, exactly as in any class. The usual design is a private final field per property, set through the constructor, with an accessor and, where useful, a static lookup method, as Speed shows in the first section.
Fields do not have to be final. But each constant is a single, program-wide object, a singleton, so any mutable field on it is shared by every piece of code that uses that constant. However a constant is reached, by its name (Config.SHARED), through valueOf or from the values() array, the result is the same object, and a method that changes a field through any of those references changes the one copy that every caller sees. This is why the single-constant enum is a common way to write a singleton, and also why mutable state on enum constants needs care in concurrent code.
Enum methods can be instance methods (called on a constant), static methods (called on the type, such as a lookup) or overrides of toString and interface methods. The methods inherited from Enum that define identity, name(), ordinal(), equals(), hashCode() and compareTo(), are final and cannot be overridden.
values(), valueOf(), ordinal(), name() and toString()
Every enum gets two compiler-generated static methods, values() and valueOf(String), and inherits ordinal(), name() and toString() from Enum.
enum Season { WINTER, SPRING, SUMMER, AUTUMN }
public class Main {
public static void main(String[] args) {
Season s = Season.valueOf("SUMMER");
Season[] copy = Season.values();
copy[3] = null;
System.out.println(s.ordinal() + " " + s.name() + " "
+ Season.values()[3] + " "
+ s.compareTo(Season.WINTER) + " "
+ (s == Season.SUMMER) + " " + s.equals("SUMMER"));
try {
Season.valueOf("summer");
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
The first line is 2 SUMMER AUTUMN 2 true false and the second is No enum constant Season.summer.
values()returns a new copy of the constants array, in declaration order, on every call. Setting an element of the copy tonullnever changes the enum, so the next call still ends withAUTUMN; two calls never return the same array object.valueOf("SUMMER")returns the constant with that exact, case-sensitive name. An unknown name throwsIllegalArgumentException; anullname throwsNullPointerException. It never returnsnull.ordinal()is the zero-based position in the declaration.==is the normal way to compare constants, andequalsgives the same answer because each constant has exactly one instance. AStringwith the same text is never equal to a constant.compareTocompares ordinals: the result is negative, zero or positive by declaration position (the JDK returns the difference of the ordinals, here 2 − 0). Sorting a list of constants (for example withCollections.sort) therefore puts them in declaration order, regardless of their names or meanings.
name() | toString() | |
|---|---|---|
| Default result | The identifier as declared | The same as name() |
| Can be overridden? | No, it is final | Yes, in the enum or in a constant body |
| Used by | valueOf, which always matches on it | String concatenation and println |
Because valueOf parses name(), overriding toString breaks the round trip: if a constant XL overrides toString to return a display label such as "Extra large", then valueOf(XL.toString()) throws IllegalArgumentException. Use name() when text must be parsed back into a constant.
Constant-specific class bodies
A constant followed by a body in braces gets its own anonymous subclass of the enum, and methods in that body override the enum's methods for that constant only. Calls are dynamically dispatched, so the same call on two constants can run different code.
enum Operation {
PLUS("+") {
int apply(int a, int b) {
return a + b;
}
},
TIMES("*") {
int apply(int a, int b) {
return a * b;
}
};
private final String symbol;
Operation(String symbol) {
this.symbol = symbol;
}
abstract int apply(int a, int b);
String symbol() {
return symbol;
}
}
public class Main {
public static void main(String[] args) {
for (Operation op : Operation.values()) {
System.out.print("3 " + op.symbol() + " 4 = "
+ op.apply(3, 4) + "; ");
}
System.out.println();
}
}
This prints 3 + 4 = 7; 3 * 4 = 12; . The enum declares apply as abstract and each constant implements it, which is legal only because every constant has a body that does so; if one constant had no implementation the enum would not compile. The constants still pass arguments to the enum's constructor, written before the body.
Two consequences of the anonymous subclass are commonly tested:
enum Alert {
INFO,
ERROR {
@Override
public String toString() {
return "Error!";
}
};
}
public class Main {
public static void main(String[] args) {
Alert e = Alert.valueOf("ERROR");
System.out.println(e + " " + e.name() + " "
+ (Alert.INFO.getClass() == Alert.class) + " "
+ (e.getClass() == Alert.INFO.getClass()) + " "
+ (e.getDeclaringClass() == Alert.class));
}
}
This prints Error! ERROR true false true. ERROR overrides toString, so it prints as Error!, while the final name() stays ERROR and valueOf("ERROR") finds it by that name. INFO has no body, so its getClass() is the enum class itself; ERROR's getClass() is its anonymous subclass, so the two constants report different classes. getDeclaringClass() exists to return the enum type for every constant, with or without a body.
A method declared only inside a constant's body cannot be called from outside that body. Each constant is declared with the enum's type, so if Tool.HAMMER's body adds a method strike() that Tool itself does not declare, Tool.HAMMER.strike() fails with cannot find symbol. Local variable type inference does not help: var infers the declared type of the expression, which is the enum type, never the anonymous subclass. To make the call possible, declare the method in the enum (abstract or with a default body) and override it in the constant.
Enums that implement interfaces
An enum can implement interfaces, either with one implementation in the enum body shared by all constants or with a separate implementation in each constant's body. Any constant can then be used wherever the interface type is expected.
public class Main {
interface Priced {
int price();
}
enum Plan implements Priced {
FREE, PRO;
public int price() {
return this == FREE ? 0 : 49;
}
}
public static void main(String[] args) {
enum Light { RED, GREEN }
Priced p = Plan.PRO;
System.out.println(p.price() + " "
+ Light.GREEN.ordinal());
}
}
This prints 49 1: Plan implements price() once for both constants, Plan.PRO is assigned to a Priced variable, and Light is a local enum declared inside main.
If the enum itself does not implement an interface method, every constant must supply it in its own body. That is legal (JLS 8.9) even though the enum class then has an unimplemented abstract method; leaving any one constant without an implementation is a compile error. Implementing interfaces is also the standard way to make enums extensible from outside: callers code against the interface and a second enum can implement it too.
Enums in switch: exhaustiveness and null
A switch expression over an enum must be exhaustive, and listing every constant satisfies that without a default. Since Java 21 the labels may be written plain (case CLUBS) or qualified (case Suit.CLUBS); both name the same constant, and several constants can share one arm. (General switch syntax, arrows versus colons and fall-through, is covered in the program-flow lesson.)
enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }
public class Main {
static String color(Suit s) {
return switch (s) {
case HEARTS, DIAMONDS -> "red";
case Suit.CLUBS -> "black";
case SPADES -> "black";
};
}
static String kind(Suit s) {
return switch (s) {
case null -> "no card";
case SPADES -> "trump";
default -> "plain";
};
}
public static void main(String[] args) {
System.out.println(color(Suit.DIAMONDS) + " "
+ kind(null) + " " + kind(Suit.CLUBS));
}
}
This prints red no card plain. color names all four constants, one of them through a qualified label, so it needs no default. A switch expression that leaves any constant unlisted and has no default fails with the switch expression does not cover all possible input values; the only cures are an arm that names the missing constant or a default.
A null selector throws NullPointerException unless the switch has a case null label. default does not match null; only case null (or the combined case null, default) does, as kind shows. A case null arm handles null only; it does nothing for exhaustiveness over the constants.
A plain switch statement over an enum, with no patterns and no case null, is exempt from exhaustiveness (JLS 14.11.2): a constant without a label simply runs nothing, and execution continues after the switch. Definite assignment is stricter. The compiler treats such a statement as possibly running no arm at all unless it has a default, so a local variable assigned only inside its arms is not definitely assigned afterwards, even when every constant is listed:
enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES }
public class Main {
public static void main(String[] args) {
Suit s = Suit.HEARTS;
String c;
switch (s) {
case HEARTS, DIAMONDS -> c = "red";
case CLUBS, SPADES -> c = "black";
}
System.out.println(c);
}
}
This fails with variable c might not have been initialized. Giving c an initial value, adding a default arm, or turning the statement into a switch expression assigned to c each makes it compile, because then c has a value on every path.
Tip. Most items show a short enum and ask what it prints, whether it compiles, or which declarations, statements or changes are legal. Expect to trace construction order against static blocks and main, to spot illegal static-field references in constructors and the indirect versions that compile but misbehave, to separate name() from an overridden toString() and to know what valueOf accepts, to reason about values() copies, ordinals and natural ordering, to follow dynamic dispatch through constant-specific bodies (including getClass() versus getDeclaringClass() and body-only methods), and to judge whether an enum switch is exhaustive and what it does with null.
- Constants come first, then a semicolon, then members; every constant's argument list must match a declared constructor.
- Enums implicitly extend Enum: they can implement interfaces but never extend a class, and no class can extend them; local enums are legal.
- Enum constructors are private or have no modifier; they can be overloaded and chained with this(...), but super(...) and new are compile errors.
- All constants are constructed once, in declaration order, at the enum's first use, before any static block or later static field initializer.
- A constructor cannot reference the enum's static fields (other constants included) except constant variables; indirect access compiles but can be wiped by a later initializer, and values() during construction throws.
- values() returns a fresh copy each call; valueOf matches name() exactly and case-sensitively, throwing IllegalArgumentException for unknown names.
- name(), ordinal(), equals() and compareTo() are final; toString() can be overridden, which breaks the valueOf(toString()) round trip.
- == and equals compare identity; compareTo and sorting follow declaration order.
- A constant body is an anonymous subclass: it can override methods and implement abstract ones, getClass() returns the subclass, and methods declared only in the body are not callable from outside.
- Switch expressions over enums must cover every constant or have default; plain switch statements need not, but a variable assigned only in a statement's arms needs a default to be definitely assigned, and default never matches null, only case null does.
Frequently asked questions
Can a Java enum have a public constructor?
No. An enum constructor is always private: one written with no modifier is treated as private, an explicit private is allowed, and public or protected causes the compile error "modifier public not allowed here". The constants declared in the enum are its only instances, so no code outside it may create one, and new on an enum type never compiles.
When are the constants of a Java enum created?
All of an enum's constants are created once, in the order they are declared, when the enum class is initialized at its first active use, for example the first time a constant is read. Because the constants are the first static fields, their constructors run before any static block or static field initializer that the enum declares after them.
Why can't an enum constructor use a static field of the enum?
When an enum's constructors run, the static fields declared after the constants have not been initialized yet, so Java makes it a compile-time error ("illegal reference to static field from initializer") to refer to them from a constructor. Constant variables, such as a static final int initialized with a literal, are the exception because their values are inlined at compile time. Code that needs all constants, such as building a lookup map, belongs in a static block after the constants.
What is the difference between name() and toString() on a Java enum?
Both return the constant's identifier by default. name() is final in java.lang.Enum and always returns the identifier exactly as declared; toString() can be overridden to print something else. valueOf always matches on name(), so if toString is overridden, passing its result to valueOf throws IllegalArgumentException.
Does Enum.valueOf ignore case?
No. The valueOf method generated for every enum returns the constant whose declared name matches the string exactly, including case. A string that matches no constant, such as "summer" for SUMMER, throws IllegalArgumentException, and a null argument throws NullPointerException; valueOf never returns null.
What does getDeclaringClass() return for an enum constant with a body?
getDeclaringClass() returns the enum type itself. A constant with a class body is an instance of an anonymous subclass of the enum, so getClass() returns that subclass and is not equal to the enum's class literal; getDeclaringClass() exists to give the enum type for every constant, with or without a body.
Does a switch over a Java enum need a default?
A switch expression over an enum must be exhaustive, but listing every constant is enough, so it needs no default. A plain switch statement over an enum without patterns or case null does not have to be exhaustive at all, though any variable it assigns must still be definitely assigned before use. In both forms a null selector throws NullPointerException unless there is a case null label, because default does not match null.
Can a Java enum extend another class or be extended?
No. Every enum implicitly extends java.lang.Enum, so it cannot declare an extends clause, and an enum cannot be the superclass of another class: an enum without constant bodies is implicitly final, and one with bodies is implicitly sealed to those bodies. An enum can implement any number of interfaces instead.
Source
This lesson covers the "Applying Object-Oriented Principles in Java Programs" domain of the official 1Z0-831 exam guide. Vendors revise their guides — check the source for the current version.
- Java SE 25 Developer Professional (1Z0-831) exam page and topics — Oracle University
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.