Enums

Su Suriya Ravichandran Updated 14 Nov 2026
8 min read ·Lesson 62 of 133

An enum (enumerated type) is a type with a fixed, known set of named values. Days of the week, order statuses, user roles, compass directions, and log levels are natural enums: the complete list of possible values is known when the code is written. Java enums are full classes. Each constant is an object, and an enum can have fields, constructors, and methods, which makes them far more capable than the integer constants used in older code.

Declaring an Enum

public enum OrderStatus {
    PLACED,
    PAID,
    SHIPPED,
    DELIVERED,
    CANCELLED
}
OrderStatus status = OrderStatus.PAID;
System.out.println(status);          // PAID

if (status == OrderStatus.PAID) {
    System.out.println("Ready to ship");
}

Constants are written in UPPER_SNAKE_CASE by convention. A variable of type OrderStatus can hold only one of these five values, or null. Nothing else compiles.

Why Not Use int or String Constants?

Before Java 5, fixed sets of values were often written as constants:

public static final int PLACED = 0;
public static final int PAID = 1;
public static final int SHIPPED = 2;

void update(int status) { ... }

update(PAID);       // intended
update(42);         // also compiles: meaningless value
update(SOME_OTHER_CONSTANT_FROM_ANOTHER_CLASS);   // compiles: wrong kind of value
Problem with int/String constantsHow enums solve it
Any int or String is accepted, including invalid onesOnly the declared constants are valid; checked at compile time
Printing shows 1 instead of a meaningful nametoString() returns the constant's name
No namespace: PAID might clash with another PAIDConstants are qualified: OrderStatus.PAID
Cannot carry data or behaviorEnums can have fields and methods
Typos in string constants ("SHIPED") are found only at runtimeTypos in enum names do not compile

Comparing Enum Values

Each enum constant is a single object, created once when the enum class is loaded. Comparing with == is therefore correct and preferred:

if (status == OrderStatus.CANCELLED) { ... }

== is also null-safe: status == OrderStatus.CANCELLED is simply false when status is null, whereas status.equals(...) would throw.

Enums in switch

Enums work naturally with switch. Inside the cases, constants are written without the type prefix:

static String message(OrderStatus s) {
    return switch (s) {
        case PLACED -> "We have received your order.";
        case PAID -> "Payment confirmed.";
        case SHIPPED -> "Your order is on its way.";
        case DELIVERED -> "Delivered. Enjoy!";
        case CANCELLED -> "This order was cancelled.";
    };
}

This switch expression has no default, yet it compiles, because it covers every constant. If a RETURNED constant is later added to the enum, the compiler reports this switch as incomplete. Leaving out default in enum switch expressions turns "forgot to handle the new status" into a compile error instead of a runtime surprise.

Built-in Methods

Every enum automatically extends java.lang.Enum and gets several methods:

MethodReturnsExample
name()The constant's exact namePAID.name() → "PAID"
ordinal()Its position, starting from 0PAID.ordinal() → 1
values() (static)An array of all constants, in declaration orderOrderStatus.values()
valueOf(String) (static)The constant with that exact nameOrderStatus.valueOf("SHIPPED")
compareTo(other)Order comparison by ordinalPAID.compareTo(SHIPPED) → negative
toString()The name, unless overriddenPAID.toString() → "PAID"
for (OrderStatus s : OrderStatus.values()) {
    System.out.println(s.ordinal() + " " + s);
}

OrderStatus parsed = OrderStatus.valueOf("DELIVERED");
OrderStatus bad = OrderStatus.valueOf("delivered");   // IllegalArgumentException: case-sensitive

valueOf throws IllegalArgumentException for an unknown name (and NullPointerException for null). When parsing user input or external data, normalize and handle the failure:

static OrderStatus parse(String text) {
    try {
        return OrderStatus.valueOf(text.strip().toUpperCase());
    } catch (IllegalArgumentException e) {
        throw new IllegalArgumentException("Unknown status: " + text);
    }
}

Avoid storing ordinal() in a database or file. Inserting a new constant in the middle of the enum shifts every later ordinal and silently corrupts stored data. Store name(), or a dedicated code field.

Fields, Constructors, and Methods

Enum constants can carry data. Each constant passes arguments to the enum's constructor:

public enum Plan {
    FREE(0, 1),
    PRO(499, 5),
    TEAM(1999, 25);

    private final int monthlyPriceInr;
    private final int maxProjects;

    Plan(int monthlyPriceInr, int maxProjects) {     // constructor is implicitly private
        this.monthlyPriceInr = monthlyPriceInr;
        this.maxProjects = maxProjects;
    }

    public int getMonthlyPriceInr() { return monthlyPriceInr; }
    public int getMaxProjects() { return maxProjects; }

    public int yearlyPriceInr() {
        return monthlyPriceInr * 10;                 // two months free on yearly billing
    }

    public boolean allows(int projects) {
        return projects <= maxProjects;
    }
}
Plan p = Plan.PRO;
System.out.println(p + " costs " + p.getMonthlyPriceInr() + "/month");   // PRO costs 499/month
System.out.println("Yearly: " + p.yearlyPriceInr());                       // Yearly: 4990
System.out.println(p.allows(8));                                           // false

Rules:

  • The list of constants must come first in the enum body, ending with a semicolon when other members follow.
  • Enum constructors are always private (explicitly or implicitly). Code outside the enum cannot create new instances; the constants are the only ones that will ever exist.
  • Fields should be final. Enum constants are shared across the entire program, so mutable state in them behaves like a global variable.

Constant-Specific Behavior

Each constant can provide its own implementation of a method, which replaces a switch with polymorphism:

public enum Operation {
    ADD("+") {
        public double apply(double a, double b) { return a + b; }
    },
    SUBTRACT("-") {
        public double apply(double a, double b) { return a - b; }
    },
    MULTIPLY("×") {
        public double apply(double a, double b) { return a * b; }
    },
    DIVIDE("÷") {
        public double apply(double a, double b) {
            if (b == 0) throw new ArithmeticException("division by zero");
            return a / b;
        }
    };

    private final String symbol;

    Operation(String symbol) { this.symbol = symbol; }

    public abstract double apply(double a, double b);

    public String symbol() { return symbol; }
}
for (Operation op : Operation.values()) {
    System.out.println("8 " + op.symbol() + " 2 = " + op.apply(8, 2));
}

Output:

8 + 2 = 10.0
8 - 2 = 6.0
8 × 2 = 16.0
8 ÷ 2 = 4.0

The abstract method forces every constant to implement apply; adding a new operation without it does not compile. Alternatively, a constructor can take a lambda: ADD("+", (a, b) -> a + b) with a field of type DoubleBinaryOperator.

Enums Implementing Interfaces

Enums cannot extend another class (they already extend Enum), but they can implement interfaces:

public interface Labeled {
    String label();
}

public enum Priority implements Labeled {
    LOW, MEDIUM, HIGH, CRITICAL;

    @Override
    public String label() {
        return name().charAt(0) + name().substring(1).toLowerCase();   // "High"
    }

    public boolean isUrgent() {
        return this.compareTo(HIGH) >= 0;          // HIGH or CRITICAL
    }
}

Declaration order defines the natural ordering used by compareTo, so placing constants from lowest to highest makes comparisons like isUrgent read naturally.

EnumSet and EnumMap

The JDK includes collections optimized for enum keys and elements:

import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;

enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }

Set<Day> weekend = EnumSet.of(Day.SAT, Day.SUN);
Set<Day> weekdays = EnumSet.complementOf(EnumSet.copyOf(weekend));
Set<Day> midweek = EnumSet.range(Day.TUE, Day.THU);

Map<Day, Integer> hoursWorked = new EnumMap<>(Day.class);
hoursWorked.put(Day.MON, 8);
hoursWorked.put(Day.TUE, 7);

System.out.println(weekdays);     // [MON, TUE, WED, THU, FRI]
System.out.println(midweek);      // [TUE, WED, THU]
System.out.println(hoursWorked);  // {MON=8, TUE=7}

EnumSet stores membership as bits, making it extremely compact and fast; EnumMap uses an array indexed by ordinal. Both iterate in declaration order. Prefer them over HashSet and HashMap whenever the keys are enum constants.

Enums as Singletons

An enum with a single constant is the simplest thread-safe singleton in Java:

public enum AppConfig {
    INSTANCE;

    private final String environment = System.getProperty("app.env", "development");

    public String environment() { return environment; }
}

System.out.println(AppConfig.INSTANCE.environment());

The JVM guarantees the constant is created exactly once, safely, even under concurrent access or serialization.

Practical Example: Order Workflow

import java.util.EnumSet;
import java.util.Set;

public class OrderWorkflow {
    enum Status {
        PLACED, PAID, SHIPPED, DELIVERED, CANCELLED;

        Set<Status> next() {
            return switch (this) {
                case PLACED -> EnumSet.of(PAID, CANCELLED);
                case PAID -> EnumSet.of(SHIPPED, CANCELLED);
                case SHIPPED -> EnumSet.of(DELIVERED);
                case DELIVERED, CANCELLED -> EnumSet.noneOf(Status.class);
            };
        }

        boolean canMoveTo(Status target) {
            return next().contains(target);
        }

        boolean isFinal() {
            return next().isEmpty();
        }
    }

    static class Order {
        private final String id;
        private Status status = Status.PLACED;

        Order(String id) { this.id = id; }

        void moveTo(Status target) {
            if (!status.canMoveTo(target)) {
                throw new IllegalStateException(
                    id + ": cannot go from " + status + " to " + target
                    + " (allowed: " + status.next() + ")");
            }
            status = target;
            System.out.println(id + " -> " + status + (status.isFinal() ? " (final)" : ""));
        }
    }

    public static void main(String[] args) {
        Order o = new Order("ORD-7");
        o.moveTo(Status.PAID);
        o.moveTo(Status.SHIPPED);
        try {
            o.moveTo(Status.CANCELLED);
        } catch (IllegalStateException e) {
            System.out.println(e.getMessage());
        }
        o.moveTo(Status.DELIVERED);
    }
}

Output:

ORD-7 -> PAID
ORD-7 -> SHIPPED
ORD-7: cannot go from SHIPPED to CANCELLED (allowed: [DELIVERED])
ORD-7 -> DELIVERED (final)

The enum holds both the set of possible statuses and the rules for moving between them. next() uses an exhaustive switch on this, so adding a new status forces its transitions to be defined. EnumSet represents the allowed targets compactly. Order cannot reach an invalid state, because every change goes through canMoveTo. A design like this, a small state machine encoded in an enum, is common for orders, payments, support tickets, and approval workflows.

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Enums and get answers drawn from it.

Signed-in readers only.