Enums and Records

By the Syncfusion DevTutorials team · Last updated

Model fixed sets of choices with enums, including fields, constructors and methods, and carry immutable data with records.

Think about the sizes printed on a T-shirt label: S, M, L and XL.

A shop never sells size "Medum" or size "7". There is a short, fixed list, and every shirt must use one of those sizes.

Now think about a printed receipt. It holds a small set of values, like an item, a price and a date. Once printed, it does not change. You only read it.

Java has a special type for each idea. An enum is a fixed list of named values, like shirt sizes. A record is a simple data holder for values that never change, like a receipt.

In simple words: use an enum for a fixed set of choices, and use a record to carry a small group of values that should stay the same.

What You Will Learn

In this lesson, you will learn:

  1. Why enums are safer than plain constants.
  2. How to declare an enum and use its constants.
  3. The built-in enum methods, such as values() and valueOf().
  4. How to give enums fields, constructors and methods.
  5. Why records exist, and how to declare one.
  6. What Java writes for you in a record.
  7. When to use a record and when to use a normal class.

Start With a Working Example

Press Run.

An enum for sizes, a record for an order line

public class Main {
    public static void main(String[] args) {
        OrderLine line = new OrderLine("T-shirt", Size.L, 499);

        System.out.println(line);
        System.out.println("Size chosen: " + line.size());
        System.out.println("Is it large? " + (line.size() == Size.L));
    }
}

enum Size {
    S, M, L, XL
}

record OrderLine(String item, Size size, int price) {
}

Expected output:

OrderLine[item=T-shirt, size=L, price=499]
Size chosen: L
Is it large? true

Size allows only four values. OrderLine holds three values, and Java wrote its constructor, its accessor methods and its readable toString() for you.

Try This

Change Size.L to Size.XXL. The program no longer compiles, because XXL is not in the list. That is exactly the safety an enum gives you.

Enumerations (enum)

Why Enums Are Needed

Before enums, programmers used plain constants for fixed choices:

static final int SMALL = 1;
static final int MEDIUM = 2;
static final int LARGE = 3;

int size = 7;   // compiles, but 7 means nothing

This has real problems:

  • Any number is accepted. Nothing stops size = 7, or size = -1.
  • Values mix up. A method expecting a size will happily take a day number.
  • Printing shows numbers. System.out.println(size) prints 2, not MEDIUM.

An enum (short for enumeration) fixes all three. It is a type whose only possible values are the ones you list.

Enum Declaration

You declare an enum with the enum keyword, then list its values, separated by commas.

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

An enum is a special kind of class. You can declare it in its own file, or next to another class.

Enum Constants

Each value in the list is an enum constant. By convention, constants are written in UPPER_CASE.

You use a constant with the enum's name and a dot: Day.MONDAY.

Enum constants are the only allowed values

public class Main {
    public static void main(String[] args) {
        Status orderStatus = Status.SHIPPED;

        System.out.println("Status: " + orderStatus);

        if (orderStatus == Status.SHIPPED) {
            System.out.println("Your order is on the way.");
        }

        switch (orderStatus) {
            case PLACED:
                System.out.println("We have your order.");
                break;
            case SHIPPED:
                System.out.println("Track it with your order ID.");
                break;
            case DELIVERED:
                System.out.println("Enjoy your purchase!");
                break;
        }
    }
}

enum Status {
    PLACED, SHIPPED, DELIVERED
}

Expected output:

Status: SHIPPED
Your order is on the way.
Track it with your order ID.

Printing an enum shows its name, not a number.

Enum constants can be compared with ==. There is exactly one Status.SHIPPED object in the whole program, so == is safe here.

In a switch, write the constant without the enum name: case SHIPPED, not case Status.SHIPPED.

Enum Methods

Every enum gets some useful methods automatically:

MethodWhat it doesExampleResult
values()Returns all constants as an array, in orderStatus.values()[PLACED, SHIPPED, DELIVERED]
valueOf(text)Turns text into the matching constantStatus.valueOf("PLACED")Status.PLACED
name()Returns the constant's name as textStatus.SHIPPED.name()"SHIPPED"
ordinal()Returns the constant's position, from 0Status.SHIPPED.ordinal()1
compareTo(other)Compares positionsPLACED.compareTo(DELIVERED)negative

Use values(), name(), ordinal() and compareTo()

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

        // values()
        for (Season s : Season.values()) {

            // name() + ordinal()
            System.out.println(
                s.name() + " -> Position: " + s.ordinal()
            );
        }

        // compareTo()
        System.out.println();

        System.out.println(
            "SUMMER vs WINTER = "
            + Season.SUMMER.compareTo(Season.WINTER)
        );

        System.out.println(
            "WINTER vs SUMMER = "
            + Season.WINTER.compareTo(Season.SUMMER)
        );
    }
}

enum Season {
    SUMMER,
    MONSOON,
    AUTUMN,
    WINTER
}

Expected output:

SUMMER -> Position: 0
MONSOON -> Position: 1
AUTUMN -> Position: 2
WINTER -> Position: 3

SUMMER vs WINTER = -3
WINTER vs SUMMER = 3

values() makes it easy to show every choice, for example in a menu.

valueOf() must get the exact name, in the same case. Season.valueOf("monsoon") stops the program with an IllegalArgumentException.

Do not save ordinal() values in files or databases. If someone later adds a new constant in the middle of the list, every position after it shifts, and the saved numbers point to the wrong values. Save name() instead.

Enum Constructors

An enum can have fields, a constructor and methods, just like a class. This lets each constant carry its own data.

You put the data in brackets after each constant. The list then ends with a ;, followed by the fields and the constructor.

enum Coin {
    ONE(1), TWO(2), FIVE(5);

    private final int value;

    Coin(int value) {
        this.value = value;
    }
}

Rules for enum constructors:

  • They are always private. You cannot write new Coin(10). Java creates each constant once, by itself.
  • They run once for each constant, when the enum is first used.

Each constant carries its own price and label

public class Main {
    public static void main(String[] args) {
        for (Plan p : Plan.values()) {
            System.out.println(p.label() + ": Rs. " + p.monthlyPrice()
                    + " a month, Rs. " + p.yearlyPrice() + " a year");
        }

        Plan chosen = Plan.STANDARD;
        System.out.println("You chose " + chosen.label());
    }
}

enum Plan {
    BASIC("Basic", 199),
    STANDARD("Standard", 499),
    PREMIUM("Premium", 799);

    private final String label;
    private final int monthlyPrice;

    Plan(String label, int monthlyPrice) {
        this.label = label;
        this.monthlyPrice = monthlyPrice;
    }

    String label() {
        return label;
    }

    int monthlyPrice() {
        return monthlyPrice;
    }

    int yearlyPrice() {
        return monthlyPrice * 10;
    }
}

Expected output:

Basic: Rs. 199 a month, Rs. 1990 a year
Standard: Rs. 499 a month, Rs. 4990 a year
Premium: Rs. 799 a month, Rs. 7990 a year
You chose Standard

BASIC("Basic", 199) calls the constructor with those two values. Each constant now knows its own label and price.

yearlyPrice() is a normal method. It works the same way on every constant, using that constant's own data.

Enums vs Constants

static final int constantsenum
Can hold an invalid value?Yes, any intNo, only the listed constants
Can mix up different groups?Yes, every group is just intNo, each enum is its own type
Prints asA number, like 2A name, like MEDIUM
Can list all values?NoYes, with values()
Can carry extra data and methods?NoYes
Works in switchYesYes

Easy Way to Remember: if you can write the full list of choices on a sticky note, and it rarely changes, use an enum.

Records

Why Records Are Needed

Many classes exist only to carry data: a point with x and y, a student with a name and marks, an API response with a status and a message.

Writing such a class by hand takes a lot of code. You need:

  • private final fields;
  • a constructor that sets them;
  • an accessor method for each field;
  • equals() and hashCode(), so two objects with the same data count as equal;
  • toString(), so the object prints its values.

That is often 30 or more lines for something simple. It is easy to get wrong, too, for example by forgetting to update equals() when a field is added.

A record does all of this in one line. Records were added in Java 16.

Record Declaration

You declare a record with the record keyword, a name, and a list of components in brackets.

record Point(int x, int y) { }

That single line gives you a complete, immutable class.

Record Components

The values inside the brackets are called record components. For each component, Java creates:

  • a private final field with the same name;
  • a public accessor method with the same name, like x().

Notice that the accessor is x(), not getX().

Records are immutable. Every field is final, and there are no setter methods. To "change" a record, you create a new one.

A one-line record, used like a full class

public class Main {
    public static void main(String[] args) {
        Student a = new Student("Asha", 92);
        Student b = new Student("Asha", 92);
        Student c = new Student("Ravi", 78);

        System.out.println(a);
        System.out.println(a.name() + " scored " + a.marks());
        System.out.println("a equals b? " + a.equals(b));
        System.out.println("a equals c? " + a.equals(c));
    }
}

record Student(String name, int marks) {
}

Expected output:

Student[name=Asha, marks=92]
Asha scored 92
a equals b? true
a equals c? false

a and b are two separate objects. But they hold the same data, so the record's automatic equals() says they are equal.

A normal class would print something like Student@1b6d3586, and equals() would only be true for the very same object.

Records were added in Java 16. They are a short way to write a final class whose fields are all private final. A record can still implement interfaces and have static methods.

Record Methods

A record can have its own methods, just like a class.

It can also have a compact constructor. This is a constructor with no parameter list after the name. It runs before the fields are set, so it is the place to check the values.

A record with a check and a helper method

public class Main {
    public static void main(String[] args) {
        Rectangle r = new Rectangle(4, 6);
        System.out.println(r);
        System.out.println("Area: " + r.area());
        System.out.println("Square? " + r.isSquare());

        Rectangle bigger = r.scale(2);
        System.out.println("Scaled: " + bigger);
        System.out.println("Original: " + r);
    }
}

record Rectangle(int width, int height) {

    Rectangle {
        if (width <= 0 || height <= 0) {
            throw new IllegalArgumentException("Sides must be positive");
        }
    }

    int area() {
        return width * height;
    }

    boolean isSquare() {
        return width == height;
    }

    Rectangle scale(int factor) {
        return new Rectangle(width * factor, height * factor);
    }
}

Expected output:

Rectangle[width=4, height=6]
Area: 24
Square? false
Scaled: Rectangle[width=8, height=12]
Original: Rectangle[width=4, height=6]

The compact constructor rejects bad sizes. new Rectangle(0, 5) would stop the program with an IllegalArgumentException.

scale() returns a new record. The original r is unchanged, because records are immutable.

throw stops the program with an error. You will learn how to handle errors in a later chapter.

What Java Writes for You

For record Point(int x, int y)Written automatically
Fieldsprivate final int x; and private final int y;
ConstructorPoint(int x, int y)
Accessorsx() and y()
equals()True when both x and y match
hashCode()Matches equals()
toString()Point[x=3, y=4]

Records vs Classes

RecordNormal class
Main jobCarry dataData plus behaviour
FieldsAll private final, set onceAny kind, can change
SettersNoneYou can write them
equals(), hashCode(), toString()Written for youYou write them yourself
Can extend another class?NoYes
Can be extended?No, records are finalYes, unless final
Can implement interfaces?YesYes
LengthOften one lineOften 30+ lines for the same data

Use a record when an object is just a group of values that should not change: coordinates, money amounts, results, settings and messages.

Use a normal class when the object's data changes over time, like a bank account balance, or when you need inheritance.

Common mistakes

The following mistakes are common when using enums and records. Each row shows what you see, why it happens and how to fix it.

SymptomCauseFix
IllegalArgumentException: No enum constantvalueOf() got text that is not an exact nameMatch the case, or use toUpperCase()
';' expected inside an enumThe constant list was not closed before the fieldsEnd the constant list with ;
enum types may not be instantiatednew was used on an enumUse an existing constant, like Plan.BASIC
cannot find symbol: method getX()Bean-style getter used on a recordUse the accessor x()
cannot assign a value to final variableA record field was changedCreate a new record with the new value
Saved data points to the wrong constantordinal() was stored and constants were reorderedStore name() instead

What to Remember

  • An enum is a type with a fixed list of named constants. Only those values are allowed.
  • Use constants with the enum name, like Status.SHIPPED, and compare them with ==.
  • values(), valueOf(), name() and ordinal() come with every enum.
  • Enums can have fields, private constructors and methods, so each constant can carry its own data.
  • A record is a one-line, immutable data class. Java writes its constructor, accessors, equals, hashCode and toString.
  • Record accessors are named x(), not getX(). Use a compact constructor to check values.
  • Use a record for plain data that never changes. Use a class when data changes or you need inheritance.

Next: Interfaces