Java Enum Tutorial: Constructors, String Values and Methods

A Java enum is a class with a fixed set of named constants. Learn how to add constructors, string values and methods to enum constants, and how to use them with switch, EnumSet and EnumMap.

Java enum

A Java enum is a special class that has a fixed set of named constants, such as EAST, WEST, NORTH and SOUTH for the four directions. We declare it with the enum keyword, and the compiler rejects any value that is not one of these constants, so an enum gives us compile-time type safety.

We should use an enum when we know all possible values of a variable at compile time or design time, though we can add more values in the future as and when we identify them. Order statuses, user roles, days of the week and log levels are typical enums in a real app.

The following example declares the Direction enum with a field and a constructor, and calls its built-in methods. The result of each line is in the comment.

enum Direction {
  EAST(0), WEST(180), NORTH(90), SOUTH(270);

  private final int angle;

  Direction(int angle) {
    this.angle = angle;
  }

  public int getAngle() {
    return angle;
  }
}

Direction west = Direction.WEST;                     // WEST
int angle = west.getAngle();                         // 180
int position = west.ordinal();                       // 1
String name = west.name();                           // "WEST"
Direction[] all = Direction.values();                // [EAST, WEST, NORTH, SOUTH]
Direction north = Direction.valueOf("NORTH");        // NORTH
boolean same = west == Direction.valueOf("WEST");    // true

Direction bad = Direction.valueOf("UP");             // IllegalArgumentException: No enum constant Direction.UP
Optional<Direction> up = Direction.fromName("UP");   // Optional.empty (helper from section 3.2)

Notice that every constant is one object, so == compares two enum values correctly, and that valueOf() throws an exception for a name that is not a constant.

Next, we look at what the compiler generates for an enum, and how we add fields, string values and methods to the constants. After that, we cover comparison, the switch statement, EnumSet and EnumMap.

1. What Is an Enum in Java?

An enumerated type is a type whose values come from a fixed list of named constants. Java added enums in JDK 1.5, and every Java enum is a class that extends java.lang.Enum.

Before Java 5, developers wrote a fixed list of values as int or String constants, and the compiler could not check them. For example, an order service stores the status as public static final int SHIPPED = 2, and a method setStatus(int status) also accepts 42 or the value of an unrelated constant. With enum OrderStatus, a method setStatus(OrderStatus status) accepts only the declared constants (or null), so a wrong value is a compile error instead of a bug in production.

1.1. The enum Keyword Is Reserved

The enum keyword is one of the reserved keywords in Java, so we cannot use it as the name of a variable or a method.

String enum = "Hello World !!";

The compiler rejects the line with the following error.

error: as of release 5, 'enum' is a keyword, and may not be used as an identifier
An IDE editor showing a compile error on the line String enum = "Hello World !!"
The word enum is a reserved keyword, so it cannot be a variable name.

1.2. Syntax to Create an Enum

The four directions have fixed names, so they fit an enum. After the enum keyword and the type name, we list the constants separated by commas. The semicolon after the last constant is optional when the enum has no other members.

enum Direction {
  EAST, WEST, NORTH, SOUTH;
}

The compiler turns Direction into a final class that extends Enum<Direction>. When we run javap -p on the compiled class, we can see that every constant is a public static final field of type Direction. The compiler also adds the static methods values() and valueOf() and a private constructor.

final class Direction extends java.lang.Enum<Direction> {
  public static final Direction EAST;
  public static final Direction WEST;
  public static final Direction NORTH;
  public static final Direction SOUTH;
  private static final Direction[] $VALUES;
  public static Direction[] values();
  public static Direction valueOf(java.lang.String);
  private Direction();
  private static Direction[] $values();
  static {};
}

The JVM creates the four Direction objects once, when it initializes the class. Each object stores its name and its position in the declaration (the ordinal), and the methods name() and ordinal() come from java.lang.Enum.

1.3. Naming Conventions

Enum constants are constants, and in Java we write constants in UPPER_CASE letters, so the same rule applies to enum constants.

  • The enum name is in title case, the same as class names, e.g. Direction or OrderStatus.
  • The constants are in UPPER_CASE with underscores between words, the same as static final constants, e.g. NORTH or OUT_FOR_DELIVERY.

1.4. Enum Variables

We declare a variable of an enum type in the same way as a variable of any class, and we assign one of the constants to it. Printing the variable prints the constant name.

Direction north = Direction.NORTH;
System.out.println(north);          // NORTH

An enum declared inside another class is a nested enum, and a nested enum is implicitly static. So we access it as Outer.Direction without an instance of the outer class.

2. Enum Constructors and Fields

By default, an enum constant holds only its name and its ordinal. When each constant needs more data, we add fields and a constructor to the enum, and we pass the values in parentheses after each constant name.

The rules for an enum constructor differ from a class constructor in two ways.

  • The constructor is always private. We can write the private modifier or leave it out, but public or protected is a compile error (“modifier public not allowed here”).
  • Only the constants call the constructor. The expression new Direction(0) is a compile error (“enum classes may not be instantiated”).

2.1. Adding a Field With a Constructor

For example, every direction has an angle on a compass. We add a final field angle with a getter, and a constructor that sets the field.

enum Direction {
  EAST(0), WEST(180), NORTH(90), SOUTH(270);

  private final int angle;

  Direction(int angle) {
    this.angle = angle;
  }

  public int getAngle() {
    return angle;
  }
}

The JVM calls the constructor once for each constant, in the order of the declaration. We read the field with the getter, the same as with any object.

Direction west = Direction.WEST;
int westAngle = west.getAngle();                  // 180
int northAngle = Direction.NORTH.getAngle();      // 90

We keep enum fields final, because every part of the app shares the same constant object, so a setter on a constant changes the value for all callers at once.

2.2. Enum With String Values and Multiple Values

An enum can store any number of values per constant, including String values. Each value is one constructor parameter and one final field. For example, a navigation app shows the short code “E” on the screen and uses the angle for the arrow, so Direction stores both.

public enum Direction {
  EAST("E", 0), WEST("W", 180), NORTH("N", 90), SOUTH("S", 270);

  private final String code;
  private final int angle;

  Direction(String code, int angle) {
    this.code = code;
    this.angle = angle;
  }

  public String getCode() {
    return code;
  }

  public int getAngle() {
    return angle;
  }
}

We get the string value of a constant from its getter.

String code = Direction.SOUTH.getCode();      // "S"
int angle = Direction.SOUTH.getAngle();       // 270

Each constant object keeps its own values of code and angle, next to the name and the ordinal that it gets from java.lang.Enum.

The Direction enum class with four boxes, one per constant object. EAST has ordinal 0, code E and angle 0. WEST has ordinal 1, code W and angle 180. NORTH has ordinal 2, code N and angle 90. SOUTH has ordinal 3, code S and angle 270. Each box is held by a public static final field, and the class extends java.lang.Enum.
Every enum constant is one object, created once when the JVM initializes the class, and it keeps its name, its ordinal and its own field values.

2.3. Getting an Enum Constant From Its Value

The reverse lookup goes from a value such as “S” back to the constant SOUTH. We need it when the value comes from outside the app, for example from a request parameter or a database column.

Java has no built-in method for the reverse lookup, so we build a Map from code to constant once, in a static field. A static method fromCode() reads the map and returns an Optional, which is empty for an unknown code or for null.

private static final Map<String, Direction> BY_CODE = Arrays.stream(values())
    .collect(Collectors.toMap(Direction::getCode, Function.identity()));

public static Optional<Direction> fromCode(String code) {
  return Optional.ofNullable(code).map(BY_CODE::get);
}

The constants are always the first static fields of an enum, so Java creates them before the BY_CODE map, and values() already returns all four constants when the map is built.

Optional<Direction> south = Direction.fromCode("S");      // Optional[SOUTH]
Optional<Direction> unknown = Direction.fromCode("X");    // Optional.empty
Optional<Direction> missing = Direction.fromCode(null);   // Optional.empty

A loop over values() also works, but it checks every constant on every call, whereas the map lookup does not get slower as the enum grows.

3. Built-in Methods

Every enum gets two kinds of methods without writing any code. The methods values() and valueOf() are static methods that the compiler adds to each enum, as we saw in the javap output in section 1.2. The instance methods name(), ordinal(), compareTo() and toString() come from java.lang.Enum.

3.1. ordinal()

The method ordinal() returns the position of a constant in the enum declaration. The first constant has ordinal 0, the same as the first index of an array.

int east = Direction.EAST.ordinal();      // 0
int north = Direction.NORTH.ordinal();    // 2

Most programmers have no use for ordinal()), because the method exists for enum-based data structures such as EnumSet and EnumMap. We never store the ordinal in a database or a file. Say a team saves the ordinal 2 for NORTH, and later a developer adds NORTH_EAST after WEST. After that change, the saved 2 reads back as NORTH_EAST, and nothing reports an error.

3.2. values() and valueOf()

The method values() returns a new array with all the constants, in the order of the declaration. We use it to loop over the constants, for example to fill a drop-down list.

for (Direction d : Direction.values()) {
  System.out.println(d + " " + d.getCode() + " " + d.getAngle());
}

The program prints one line per constant.

EAST E 0
WEST W 180
NORTH N 90
SOUTH S 270

The method valueOf() converts a String to an enum constant. The text must be the exact constant name, including the case, so “north” does not match NORTH. The method either returns the constant or throws an exception.

Direction north = Direction.valueOf("NORTH");    // NORTH
Direction lower = Direction.valueOf("north");    // IllegalArgumentException: No enum constant Direction.north
Direction empty = Direction.valueOf(null);       // NullPointerException: Name is null

Text from a user or a URL parameter can contain anything, so we wrap valueOf() in a helper that returns an Optional. The helper also removes spaces with strip() and converts the text to upper case before the lookup.

public static Optional<Direction> fromName(String name) {
  if (name == null) {
    return Optional.empty();
  }
  try {
    return Optional.of(valueOf(name.strip().toUpperCase()));
  } catch (IllegalArgumentException e) {
    return Optional.empty();
  }
}
Optional<Direction> north = Direction.fromName(" north ");    // Optional[NORTH]
Optional<Direction> up = Direction.fromName("UP");             // Optional.empty
Optional<Direction> missing = Direction.fromName(null);        // Optional.empty

3.3. name(), toString() and compareTo()

The method name() returns the constant name as declared, and it is final, so no enum can change it. The method toString() returns the same name by default, but we can override it, for example to return “East” for a screen label. So we call name() when the text must go back into valueOf(), and toString() for display.

The method compareTo() compares two constants by their ordinals. Every enum implements Comparable, so sorting a list of constants puts them in declaration order, which is the natural order of an enum.

String name = Direction.WEST.name();                                   // "WEST"
int beforeNorth = Direction.EAST.compareTo(Direction.NORTH);           // -2 (EAST comes first)
int afterWest = Direction.SOUTH.compareTo(Direction.WEST);             // 2 (SOUTH comes later)

4. Custom Methods

An enum is a class, so it can have methods and fields like any other class. We can add both concrete and abstract methods.

4.1. Non-abstract Methods

Adding a concrete method to an enum is the same as adding it to a class. We can use any access modifier, such as public or private. Inside the method, this is the constant that we called the method on.

public enum Direction {
  EAST, WEST, NORTH, SOUTH;

  public String message() {
    return "Moving in " + this + " direction";
  }
}

We call message() on a constant, the same as on any object.

String message = Direction.NORTH.message();    // "Moving in NORTH direction"

4.2. Abstract Methods

When the behavior differs per constant, we declare an abstract method in the enum. Each constant has its own body in braces, where it implements the method, and the compiler reports an error when a constant does not implement it.

public enum Direction {
  EAST {
    @Override
    public String message() {
      return "You are moving in east. You will face sun in morning time.";
    }
  },
  WEST {
    @Override
    public String message() {
      return "You are moving in west. You will face sun in evening time.";
    }
  },
  NORTH {
    @Override
    public String message() {
      return "You are moving in north. Sea behind.";
    }
  },
  SOUTH {
    @Override
    public String message() {
      return "You are moving in south. Sea ahead.";
    }
  };

  public abstract String message();
}

Each call runs the body of the constant that we call it on.

String west = Direction.WEST.message();     // "You are moving in west. You will face sun in evening time."
String north = Direction.NORTH.message();   // "You are moving in north. Sea behind."

The abstract method works as a contract. When a developer adds a new constant, such as NORTH_EAST, the code does not compile until the new constant implements message(). Each constant body is an anonymous subclass of Direction, so Direction.WEST.getClass() returns a class such as Direction$2, whereas getDeclaringClass() returns Direction.

5. Inheritance

Every enum extends java.lang.Enum, which is the abstract base class of all Java enum types. In Java 25, the class implements three interfaces.

public abstract class Enum<E extends Enum<E>>
        implements Constable, Comparable<E>, Serializable {
  //...
}

So all enums are comparable and serializable. Java creates each constant only once, and the serialization mechanism and reflection never create a second copy, so an enum with one constant is a safe way to write a singleton.

An enum cannot extend another class, because it already extends java.lang.Enum, and Java does not support multiple inheritance of classes. An enum can implement any number of interfaces, so code that works with an interface type can take enum constants too.

An enum is also implicitly final. When at least one constant has a body, as in section 4.2, the enum is sealed instead, and only its constant bodies can extend it.

6. Comparing Two Enums

Java creates only one object per constant, so we can compare enum values with the == operator or with equals(), and both return the same result.

Direction east = Direction.EAST;
Direction eastNew = Direction.valueOf("EAST");

boolean sameObject = east == eastNew;           // true
boolean equal = east.equals(eastNew);           // true

We prefer == for enums, because it is safe with null. Say the direction comes from a form field that the user left empty, so the variable is null. The check missing == Direction.EAST returns false, whereas missing.equals(Direction.EAST) throws a NullPointerException.

Direction missing = null;

boolean viaOperator = missing == Direction.EAST;          // false
boolean constantFirst = Direction.EAST.equals(missing);   // false
boolean viaEquals = missing.equals(Direction.EAST);       // NullPointerException

The == operator also checks the types at compile time, so comparing a Direction with a constant of a different enum is a compile error, whereas equals() compiles and returns false.

7. Using an Enum in a switch

We can use an enum in a switch, the same as an int or a String. Inside the case labels, we write the constant name without the enum type, e.g. case NORTH.

With the arrow syntax of switch expressions (Java 14 and later), the compiler checks that the cases cover every constant. For example, turnRight() returns the next direction clockwise and needs no default branch.

public Direction turnRight() {
  return switch (this) {
    case NORTH -> EAST;
    case EAST -> SOUTH;
    case SOUTH -> WEST;
    case WEST -> NORTH;
  };
}
Direction next = Direction.NORTH.turnRight();     // EAST
Direction afterWest = Direction.WEST.turnRight(); // NORTH

The exhaustive check pays off when the enum grows. If a developer adds NORTH_EAST and forgets the switch, the build fails with “the switch expression does not cover all possible input values”. A default branch would hide the missing case, so we leave it out when the switch covers every constant. A switch on a null enum variable throws a NullPointerException.

8. EnumSet and EnumMap

The java.util package has two collections built for enum types. EnumSet is a Set of constants from one enum, and EnumMap is a Map with enum keys. Both use the ordinals internally (a bit vector in EnumSet, an array in EnumMap), so they use less memory than a HashSet or a HashMap with the same content, and in most cases they are faster too.

8.1. EnumSet

EnumSet has no public constructor, so we create a set with one of its static factory methods. All elements must come from one enum type, and the set keeps them in declaration order, whatever the order of the arguments.

Set<Direction> horizontal = EnumSet.of(Direction.WEST, Direction.EAST);                 // [EAST, WEST]
Set<Direction> all = EnumSet.allOf(Direction.class);                                    // [EAST, WEST, NORTH, SOUTH]
Set<Direction> vertical = EnumSet.complementOf(EnumSet.of(Direction.EAST, Direction.WEST));  // [NORTH, SOUTH]
Set<Direction> range = EnumSet.range(Direction.WEST, Direction.SOUTH);                  // [WEST, NORTH, SOUTH]
Set<Direction> none = EnumSet.noneOf(Direction.class);                                  // []
boolean hasEast = horizontal.contains(Direction.EAST);                                  // true

An EnumSet does not accept null elements, so horizontal.add(null) throws a NullPointerException. Like most collections, EnumSet is not synchronized. When several threads change the same set, we wrap it with Collections.synchronizedSet().

8.2. EnumMap

EnumMap is a Map whose keys come from one enum type, which we pass to the constructor. The map keeps the keys in declaration order. For example, a delivery app counts the trips per direction.

Map<Direction, Integer> trips = new EnumMap<>(Direction.class);
trips.put(Direction.SOUTH, 3);
trips.put(Direction.EAST, 5);
trips.merge(Direction.EAST, 1, Integer::sum);                   // {EAST=6, SOUTH=3}

Integer northTrips = trips.get(Direction.NORTH);                // null
int northOrZero = trips.getOrDefault(Direction.NORTH, 0);       // 0

Like EnumSet, an EnumMap is not synchronized and does not accept null keys, so trips.put(null, 1) throws a NullPointerException. A null value is allowed.

9. Java Enum FAQs

9.1. Can an Enum Constructor Be Public?

No. An enum constructor is always private, as we saw in section 2. The modifiers public and protected are compile errors, and a constructor without a modifier is private too. Only the enum constants may create instances, so every enum constructor is private.

9.2. How Do We Store an Enum in a Database or in JSON?

We store the name, not the ordinal, for the reason in section 3.1. With JPA, we add @Enumerated(EnumType.STRING) to the field, because the default EnumType.ORDINAL saves the ordinal. Jackson writes an enum as its name() by default, e.g. “NORTH”, and reads it back with the same name.

9.3. Can We Add Enum Constants at Runtime?

No. The constants are fixed when the enum is compiled, and values() always returns the same list. When the list of values changes at runtime, for example product categories that an admin edits, we store the values in a database table instead of an enum.

10. Conclusion

A Java enum is a class with a fixed set of constants, and Java creates one object per constant. The compiler adds values() and valueOf(), and the class inherits name(), ordinal(), compareTo() and toString() from java.lang.Enum.

We add fields and a private constructor when the constants need data, such as a string code or an angle. For the reverse lookup from a value to a constant, we build a static Map and return an Optional, and we wrap valueOf() the same way for user input.

Enum constants are safe to compare with ==, and a switch expression checks at compile time that we handled every constant. For sets and maps of constants, EnumSet and EnumMap use less memory than HashSet and HashMap.

11. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.