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.

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).

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.

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 } } // 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

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.

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 } }

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.

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.

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.

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 // ... }

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.

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.

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

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.