Skip to main content
Java · lesson 59 of 66
Open the Java page →
Java intermediate Lesson 59 of 66

Java Enums: Beyond Simple Constants

Master Java enums with fields, constructors, abstract methods, and methods — when to use them over constants and how they enable type-safe state machines.

By SkillByExample · Updated

An enum is a class with a fixed set of instances, one per constant. Because it is a real class, each constant can carry fields, the enum can have methods, and constants can even override methods individually. Every example is a single file run with the JDK 21 source launcher (java File.java).

Enums are type-safe

Compare an enum with the int constants it replaces.

public class TypeSafety {
    static final int MONDAY = 1, SATURDAY = 6;
    enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

    static void scheduleInt(int day) { System.out.println("int day " + day); }
    static void schedule(Day day)    { System.out.println("enum day " + day); }

    public static void main(String[] args) {
        scheduleInt(SATURDAY);
        scheduleInt(42);                // compiles: any int is accepted
        schedule(Day.SATURDAY);
        schedule(42);
    }
}
$ java TypeSafety.java
TypeSafety.java:12: error: method schedule in class TypeSafety cannot be applied to given types;
        schedule(42);
        ^
  required: Day
  found:    int
  reason: argument mismatch; int cannot be converted to Day
1 error
error: compilation failed

scheduleInt(42) is accepted even though 42 is not a day. With the enum, only the seven Day constants (or null) can ever be passed. Remove the last line and the program prints int day 6, int day 42, enum day SATURDAY.

Constants with fields

Each constant passes arguments to a constructor, which is implicitly private and runs once per constant when the enum is loaded.

public class Planets {
    enum Planet {
        MERCURY(3.303e+23, 2.4397e6),
        EARTH(5.976e+24, 6.37814e6),
        MARS(6.421e+23, 3.3972e6);

        private final double mass, radius;
        Planet(double mass, double radius) { this.mass = mass; this.radius = radius; }

        double surfaceGravity() { return 6.67300E-11 * mass / (radius * radius); }
    }

    public static void main(String[] args) {
        for (Planet p : Planet.values()) {
            System.out.printf("%-8s %5.2f m/s^2%n", p, p.surfaceGravity());
        }
        double earthWeight = 70;
        double mass = earthWeight / Planet.EARTH.surfaceGravity();
        System.out.printf("70 kg-weight on Mars: %.1f%n", mass * Planet.MARS.surfaceGravity());
    }
}
$ java Planets.java
MERCURY   3.70 m/s^2
EARTH     9.80 m/s^2
MARS      3.71 m/s^2
70 kg-weight on Mars: 26.5

The data lives with the constant, so there is no parallel lookup table to keep in sync. values() returns the constants in declaration order.

Built-in methods: values, valueOf, name, ordinal

public class BuiltIns {
    enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

    public static void main(String[] args) {
        System.out.println(Day.values().length + " days, first " + Day.values()[0]);
        Day d = Day.valueOf("FRIDAY");
        System.out.println(d.name() + " ordinal=" + d.ordinal() + " compareTo(MONDAY)=" + d.compareTo(Day.MONDAY));
        System.out.println(Day.valueOf("friday".toUpperCase()));
        System.out.println(Day.valueOf("Friday"));
    }
}
$ java BuiltIns.java
7 days, first MONDAY
FRIDAY ordinal=4 compareTo(MONDAY)=4
FRIDAY
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant BuiltIns.Day.Friday
	at java.base/java.lang.Enum.valueOf(Enum.java:293)
	at BuiltIns$Day.valueOf(BuiltIns.java:2)
	at BuiltIns.main(BuiltIns.java:9)

valueOf is exact and case-sensitive, so normalise input before calling it. ordinal() is the zero-based position, and enums compare by it. Don’t store ordinals in a database: inserting a constant in the middle renumbers everything after it.

Methods and switch

Constants are singletons, so == compares them safely, and a switch expression over an enum needs no default when it lists every constant.

public class Weekend {
    enum Day {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;
        boolean isWeekend() { return this == SATURDAY || this == SUNDAY; }
    }

    static int hours(Day d) {
        return switch (d) {
            case MONDAY, TUESDAY, WEDNESDAY, THURSDAY -> 8;
            case FRIDAY -> 6;
            case SATURDAY, SUNDAY -> 0;
        };
    }

    public static void main(String[] args) {
        for (Day d : Day.values()) System.out.print(d + "=" + hours(d) + (d.isWeekend() ? "* " : " "));
        System.out.println();
    }
}
$ java Weekend.java
MONDAY=8 TUESDAY=8 WEDNESDAY=8 THURSDAY=8 FRIDAY=6 SATURDAY=0* SUNDAY=0* 

The switch is exhaustive: delete the SATURDAY, SUNDAY line and compilation fails with “the switch expression does not cover all possible input values”. That error is the reason to prefer listing constants over a default — adding an eighth constant later flags every switch that forgot it.

Constant-specific behaviour

An enum can declare an abstract method that every constant implements in its own body.

public class Calculator {
    enum Operation {
        PLUS("+")  { double apply(double x, double y) { return x + y; } },
        MINUS("-") { double apply(double x, double y) { return x - y; } },
        TIMES("*") { double apply(double x, double y) { return x * y; } },
        DIVIDE("/") { double apply(double x, double y) { return x / y; } };

        final String symbol;
        Operation(String symbol) { this.symbol = symbol; }
        abstract double apply(double x, double y);
    }

    public static void main(String[] args) {
        for (Operation op : Operation.values()) {
            System.out.println("10 " + op.symbol + " 4 = " + op.apply(10, 4));
        }
    }
}
$ java Calculator.java
10 + 4 = 14.0
10 - 4 = 6.0
10 * 4 = 40.0
10 / 4 = 2.5

Each constant is an anonymous subclass of Operation. Forgetting apply on a new constant is a compile error, unlike a switch elsewhere that someone forgets to update.

EnumMap and EnumSet

The collections specialised for enum keys are compact arrays and bit sets internally, and they iterate in declaration order.

import java.util.*;

public class EnumCollections {
    enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

    public static void main(String[] args) {
        EnumSet<Day> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY);
        EnumSet<Day> weekdays = EnumSet.complementOf(weekend);
        System.out.println(weekdays);
        System.out.println(EnumSet.range(Day.TUESDAY, Day.THURSDAY));

        Map<Day, Integer> meetings = new EnumMap<>(Day.class);
        meetings.put(Day.FRIDAY, 1);
        meetings.put(Day.MONDAY, 3);
        System.out.println(meetings);
    }
}
$ java EnumCollections.java
[MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY]
[TUESDAY, WEDNESDAY, THURSDAY]
{MONDAY=3, FRIDAY=1}

FRIDAY was put first, but the EnumMap prints MONDAY first: it orders by declaration, not insertion. Use these instead of HashSet/HashMap whenever the keys are enum constants.

Practice

  1. Write a LogLevel enum — ERROR(4), WARN(3), INFO(2), DEBUG(1) — with shouldLog(LogLevel threshold). Print which levels are logged when the threshold is WARN.
Solution
public class Levels {
    enum LogLevel {
        ERROR(4), WARN(3), INFO(2), DEBUG(1);
        private final int severity;
        LogLevel(int severity) { this.severity = severity; }
        boolean shouldLog(LogLevel threshold) { return severity >= threshold.severity; }
    }

    public static void main(String[] args) {
        for (LogLevel l : LogLevel.values()) {
            System.out.println(l + " logged? " + l.shouldLog(LogLevel.WARN));
        }
    }
}
$ java Levels.java
ERROR logged? true
WARN logged? true
INFO logged? false
DEBUG logged? false

Taking a LogLevel rather than a raw int keeps the threshold type-safe too.

  1. Model a traffic light as an enum where each constant implements next(), and print six transitions starting from RED.
Solution
public class Traffic {
    enum Light {
        RED    { Light next() { return GREEN; } },
        GREEN  { Light next() { return YELLOW; } },
        YELLOW { Light next() { return RED; } };
        abstract Light next();
    }

    public static void main(String[] args) {
        Light l = Light.RED;
        StringBuilder sb = new StringBuilder(l.toString());
        for (int i = 0; i < 6; i++) { l = l.next(); sb.append(" -> ").append(l); }
        System.out.println(sb);
    }
}
$ java Traffic.java
RED -> GREEN -> YELLOW -> RED -> GREEN -> YELLOW -> RED

A state machine as an enum: every state must define its transition, and no invalid state can exist.

  1. Write parseDay(String) that accepts any case and returns an Optional<Day>, empty for unknown names instead of throwing.
Solution
import java.util.Optional;

public class ParseDay {
    enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

    static Optional<Day> parseDay(String s) {
        try {
            return Optional.of(Day.valueOf(s.strip().toUpperCase()));
        } catch (IllegalArgumentException e) {
            return Optional.empty();
        }
    }

    public static void main(String[] args) {
        System.out.println(parseDay(" friday "));
        System.out.println(parseDay("Funday"));
    }
}
$ java ParseDay.java
Optional[FRIDAY]
Optional.empty

Catching the exception at the boundary keeps the rest of the code free of string checks.

  1. Count words by their Day in "MONDAY FRIDAY MONDAY SUNDAY" using an EnumMap.
Solution
import java.util.EnumMap;
import java.util.Map;

public class DayCount {
    enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }

    public static void main(String[] args) {
        Map<Day, Integer> counts = new EnumMap<>(Day.class);
        for (String w : "MONDAY FRIDAY MONDAY SUNDAY".split(" ")) counts.merge(Day.valueOf(w), 1, Integer::sum);
        System.out.println(counts);
    }
}
$ java DayCount.java
{MONDAY=2, FRIDAY=1, SUNDAY=1}

merge works on an EnumMap like any other map, and the result comes out in week order.

Frequently Asked Questions

Can enums have fields and constructors?
Yes. Each enum constant can carry its own fields (like a planet's mass or radius) and a private constructor that initializes them. The constructor runs once per constant at load time.
Can an enum have abstract methods?
Yes — an enum can declare an abstract method and each constant provides its own implementation. This turns the enum into a type-safe state machine where each constant defines its own behavior.
When should I use an enum instead of int constants?
Almost always. Enums are type-safe (the compiler rejects invalid values), iterable with values(), can carry fields and methods, and work with exhaustive switch expressions. Int constants offer none of that.
What does Enum.valueOf do with an unknown name?
It throws IllegalArgumentException with a message like 'No enum constant Day.FUNDAY'. The match is exact and case-sensitive, so normalise user input (for example with toUpperCase) and catch the exception when the text comes from outside the program.