Enums: Fields, Methods and Patterns

Java enums are far more powerful than the simple named constants found in other languages. Each enum constant is a full object: it can have fields, constructors, and methods. You can even give each constant its own method body. This makes enums an elegant solution to patterns that would otherwise require complex class hierarchies.

Basic Enum

The simplest enum is a list of named constants. The compiler generates a class that extends java.lang.Enum, making every constant a singleton instance.

Java
public enum Direction {
    NORTH, SOUTH, EAST, WEST
}

public class Main {
    public static void main(String[] args) {
        Direction d = Direction.NORTH;

        // name() returns the declared identifier as a String
        System.out.println(d.name());       // NORTH

        // ordinal() returns the zero-based position
        System.out.println(d.ordinal());    // 0

        // valueOf() looks up a constant by its name
        Direction east = Direction.valueOf("EAST");
        System.out.println(east);           // EAST

        // == is safe for enums — constants are singletons
        System.out.println(d == Direction.NORTH);  // true

        // toString() defaults to name()
        System.out.println(Direction.WEST.toString()); // WEST
    }
}

Every enum implicitly implements java.io.Serializable and java.lang.Comparable. The natural ordering is the declaration order (by ordinal).

Note:

Enum with Fields and Constructor

Each enum constant can carry data. You add fields and a constructor to the enum body. The constructor is always implicitly private (enums cannot be instantiated externally), and each constant's arguments are passed in parentheses after its name.

Java
public enum Planet {

    MERCURY(3.303e+23, 2.4397e6),
    VENUS  (4.869e+24, 6.0518e6),
    EARTH  (5.976e+24, 6.37814e6),
    MARS   (6.421e+23, 3.3972e6);

    private final double mass;   // kg
    private final double radius; // metres

    // Constructor — always private
    Planet(double mass, double radius) {
        this.mass   = mass;
        this.radius = radius;
    }

    static final double G = 6.67300E-11;

    double surfaceGravity() {
        return G * mass / (radius * radius);
    }

    double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }
}

public class Main {
    public static void main(String[] args) {
        double earthWeight = 75.0; // kg
        double mass = earthWeight / Planet.EARTH.surfaceGravity();

        for (Planet p : Planet.values()) {
            System.out.printf("Weight on %-10s: %.2f N%n",
                              p, p.surfaceWeight(mass));
        }
        // Weight on MERCURY   : 28.33 N
        // Weight on VENUS     : 67.90 N
        // Weight on EARTH     : 75.00 N
        // Weight on MARS      : 28.46 N
    }
}
Java
// Enum with a display label different from the constant name
public enum HttpStatus {

    OK(200, "OK"),
    CREATED(201, "Created"),
    BAD_REQUEST(400, "Bad Request"),
    UNAUTHORIZED(401, "Unauthorized"),
    NOT_FOUND(404, "Not Found"),
    INTERNAL_SERVER_ERROR(500, "Internal Server Error");

    private final int code;
    private final String reason;

    HttpStatus(int code, String reason) {
        this.code   = code;
        this.reason = reason;
    }

    public int    code()   { return code; }
    public String reason() { return reason; }

    // Reverse lookup — find enum by numeric code
    public static HttpStatus fromCode(int code) {
        for (HttpStatus s : values()) {
            if (s.code == code) return s;
        }
        throw new IllegalArgumentException("Unknown status code: " + code);
    }

    @Override
    public String toString() {
        return code + " " + reason;
    }
}

// Usage
System.out.println(HttpStatus.NOT_FOUND);           // 404 Not Found
System.out.println(HttpStatus.fromCode(201));       // 201 Created
System.out.println(HttpStatus.OK.code());           // 200

Enum Methods

Enums can define any non-abstract method in the enum body. These methods operate on the constant's fields and can call other methods. You can also override toString() to control how constants are printed.

Java
public enum Day {

    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY;

    public boolean isWeekend() {
        return this == SATURDAY || this == SUNDAY;
    }

    public boolean isWeekday() {
        return !isWeekend();
    }

    // The next day, wrapping around
    public Day next() {
        Day[] values = Day.values();
        return values[(this.ordinal() + 1) % values.length];
    }

    public Day previous() {
        Day[] values = Day.values();
        int prev = (this.ordinal() - 1 + values.length) % values.length;
        return values[prev];
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println(Day.FRIDAY.isWeekend());    // false
        System.out.println(Day.SATURDAY.isWeekend());  // true
        System.out.println(Day.SUNDAY.next());         // MONDAY
        System.out.println(Day.MONDAY.previous());     // SUNDAY

        // Print only weekdays
        for (Day d : Day.values()) {
            if (d.isWeekday()) System.out.print(d + " ");
        }
        // MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY
    }
}

Enum with Abstract Methods

Each constant can provide its own implementation of an abstract method declared in the enum body. This is the constant-specific method body pattern: essentially a compile-time-verified strategy pattern without subclasses.

Java
public enum Operation {

    PLUS("+") {
        @Override
        public double apply(double x, double y) { return x + y; }
    },
    MINUS("-") {
        @Override
        public double apply(double x, double y) { return x - y; }
    },
    TIMES("*") {
        @Override
        public double apply(double x, double y) { return x * y; }
    },
    DIVIDE("/") {
        @Override
        public double apply(double x, double y) {
            if (y == 0) throw new ArithmeticException("Division by zero");
            return x / y;
        }
    };

    private final String symbol;

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

    // Abstract — every constant must implement this
    public abstract double apply(double x, double y);

    @Override
    public String toString() { return symbol; }
}

public class Main {
    public static void main(String[] args) {
        double x = 10, y = 3;
        for (Operation op : Operation.values()) {
            System.out.printf("%.1f %s %.1f = %.2f%n",
                              x, op, y, op.apply(x, y));
        }
        // 10.0 + 3.0 = 13.00
        // 10.0 - 3.0 = 7.00
        // 10.0 * 3.0 = 30.00
        // 10.0 / 3.0 = 3.33
    }
}

This pattern is more maintainable than a switch on this inside the method body: adding a new constant forces you to implement apply, preventing accidental omissions.

Enums can also implement interfaces. Combining interface implementation with constant-specific method bodies is a powerful way to attach polymorphic behaviour to a closed set of constants.

Switch on Enum

Enums are the ideal switch target. The compiler can verify exhaustiveness, and there is no need to handle a default case when all constants are covered. Java 14+ switch expressions make this even cleaner.

Java
public enum Season { SPRING, SUMMER, AUTUMN, WINTER }

// Traditional switch statement
public static String describeOld(Season s) {
    switch (s) {
        case SPRING: return "Mild and blooming";
        case SUMMER: return "Hot and sunny";
        case AUTUMN: return "Cool and colourful";
        case WINTER: return "Cold and snowy";
        default:     return "Unknown";
    }
}

// Modern switch expression (Java 14+) — exhaustive, no fall-through
public static String describe(Season s) {
    return switch (s) {
        case SPRING -> "Mild and blooming";
        case SUMMER -> "Hot and sunny";
        case AUTUMN -> "Cool and colourful";
        case WINTER -> "Cold and snowy";
        // No default needed — all four constants are handled
    };
}

// Switch with yield for multi-line arms
public static int monthsInSeason(Season s) {
    return switch (s) {
        case SPRING, SUMMER, AUTUMN, WINTER -> {
            int months = 3;
            System.out.println(s + " has " + months + " months");
            yield months;
        }
    };
}

public static void main(String[] args) {
    for (Season s : Season.values()) {
        System.out.println(s + ": " + describe(s));
    }
    // SPRING: Mild and blooming
    // SUMMER: Hot and sunny
    // ...
}

EnumSet and EnumMap

java.util.EnumSet and java.util.EnumMap are specialised, high-performance implementations of Set and Map for enum keys. They are backed by bit vectors and arrays respectively, making them faster and more memory-efficient than their general-purpose counterparts.

Java
import java.util.*;

public enum Permission {
    READ, WRITE, DELETE, ADMIN
}

public class PermissionSystem {

    // EnumSet — fast set operations using bit manipulation
    public static void demonstrateEnumSet() {

        EnumSet<Permission> userPerms  = EnumSet.of(Permission.READ, Permission.WRITE);
        EnumSet<Permission> adminPerms = EnumSet.allOf(Permission.class);
        EnumSet<Permission> noPerms    = EnumSet.noneOf(Permission.class);
        EnumSet<Permission> readOnly   = EnumSet.of(Permission.READ);

        // Complement — everything except READ
        EnumSet<Permission> notRead = EnumSet.complementOf(readOnly);
        System.out.println("Not read: " + notRead); // [WRITE, DELETE, ADMIN]

        // Range — ordinal order (READ=0, WRITE=1, DELETE=2)
        EnumSet<Permission> basicRange = EnumSet.range(Permission.READ, Permission.DELETE);
        System.out.println("Range: " + basicRange); // [READ, WRITE, DELETE]

        // Test membership — O(1)
        System.out.println(userPerms.contains(Permission.DELETE)); // false
        System.out.println(adminPerms.containsAll(userPerms));     // true
    }

    // EnumMap — map from enum constants to values
    public static void demonstrateEnumMap() {
        EnumMap<Permission, String> descriptions = new EnumMap<>(Permission.class);
        descriptions.put(Permission.READ,   "Can view resources");
        descriptions.put(Permission.WRITE,  "Can create and update resources");
        descriptions.put(Permission.DELETE, "Can remove resources");
        descriptions.put(Permission.ADMIN,  "Full system access");

        // Iteration always follows declaration order
        descriptions.forEach((perm, desc) ->
            System.out.printf("%-10s : %s%n", perm, desc));
    }

    public static void main(String[] args) {
        demonstrateEnumSet();
        System.out.println("---");
        demonstrateEnumMap();
    }
}

The internal representation of EnumSet uses a single long for enums with 64 or fewer constants (RegularEnumSet). Each bit corresponds to an ordinal. Set operations like addAll, retainAll, and containsAll become single bitwise operations.

Note:

Iterating Enum Values

The compiler-generated values() method returns a fresh array of all constants in declaration order. valueOf(String) performs a reverse lookup by name.

Java
public enum Month {
    JANUARY(31), FEBRUARY(28), MARCH(31), APRIL(30),
    MAY(31), JUNE(30), JULY(31), AUGUST(31),
    SEPTEMBER(30), OCTOBER(31), NOVEMBER(30), DECEMBER(31);

    private final int days;

    Month(int days) { this.days = days; }

    public int days() { return days; }

    public int days(int year) {
        // Leap year only affects February
        if (this == FEBRUARY) {
            return isLeapYear(year) ? 29 : 28;
        }
        return days;
    }

    private static boolean isLeapYear(int year) {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }
}

public class Main {
    public static void main(String[] args) {

        // Iterate all values
        int totalDays = 0;
        for (Month m : Month.values()) {
            totalDays += m.days();
        }
        System.out.println("Days in a normal year: " + totalDays); // 365

        // Use streams on values()
        long longMonths = java.util.Arrays.stream(Month.values())
                                          .filter(m -> m.days() == 31)
                                          .count();
        System.out.println("Months with 31 days: " + longMonths); // 7

        // Lookup by name
        Month m = Month.valueOf("MARCH");
        System.out.println(m.days(2024)); // 31

        // February 2024 (leap year)
        System.out.println(Month.FEBRUARY.days(2024)); // 29
        System.out.println(Month.FEBRUARY.days(2023)); // 28

        // Build a lookup map (more efficient than calling valueOf() in a loop)
        java.util.Map<Integer, Month> byOrdinal = new java.util.EnumMap<>(Month.class);
        for (Month mo : Month.values()) {
            byOrdinal.put(mo.ordinal() + 1, mo); // 1-based month number
        }
        System.out.println(byOrdinal.get(12)); // DECEMBER
    }
}
values() allocates a new array on each call. For performance-sensitive loops inside tight hot spots, cache the array in a local variable or a static constant rather than calling values() repeatedly.