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:
- Why enums are safer than plain constants.
- How to declare an enum and use its constants.
- The built-in enum methods, such as
values()andvalueOf(). - How to give enums fields, constructors and methods.
- Why records exist, and how to declare one.
- What Java writes for you in a record.
- 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, orsize = -1. - Values mix up. A method expecting a size will happily take a day number.
- Printing shows numbers.
System.out.println(size)prints2, notMEDIUM.
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:
| Method | What it does | Example | Result |
|---|---|---|---|
values() | Returns all constants as an array, in order | Status.values() | [PLACED, SHIPPED, DELIVERED] |
valueOf(text) | Turns text into the matching constant | Status.valueOf("PLACED") | Status.PLACED |
name() | Returns the constant's name as text | Status.SHIPPED.name() | "SHIPPED" |
ordinal() | Returns the constant's position, from 0 | Status.SHIPPED.ordinal() | 1 |
compareTo(other) | Compares positions | PLACED.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 writenew 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 constants | enum | |
|---|---|---|
| Can hold an invalid value? | Yes, any int | No, only the listed constants |
| Can mix up different groups? | Yes, every group is just int | No, each enum is its own type |
| Prints as | A number, like 2 | A name, like MEDIUM |
| Can list all values? | No | Yes, with values() |
| Can carry extra data and methods? | No | Yes |
Works in switch | Yes | Yes |
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 finalfields;- a constructor that sets them;
- an accessor method for each field;
equals()andhashCode(), 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 finalfield 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 |
|---|---|
| Fields | private final int x; and private final int y; |
| Constructor | Point(int x, int y) |
| Accessors | x() and y() |
equals() | True when both x and y match |
hashCode() | Matches equals() |
toString() | Point[x=3, y=4] |
Records vs Classes
| Record | Normal class | |
|---|---|---|
| Main job | Carry data | Data plus behaviour |
| Fields | All private final, set once | Any kind, can change |
| Setters | None | You can write them |
equals(), hashCode(), toString() | Written for you | You write them yourself |
| Can extend another class? | No | Yes |
| Can be extended? | No, records are final | Yes, unless final |
| Can implement interfaces? | Yes | Yes |
| Length | Often one line | Often 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.
| Symptom | Cause | Fix |
|---|---|---|
IllegalArgumentException: No enum constant | valueOf() got text that is not an exact name | Match the case, or use toUpperCase() |
';' expected inside an enum | The constant list was not closed before the fields | End the constant list with ; |
enum types may not be instantiated | new was used on an enum | Use an existing constant, like Plan.BASIC |
cannot find symbol: method getX() | Bean-style getter used on a record | Use the accessor x() |
cannot assign a value to final variable | A record field was changed | Create a new record with the new value |
| Saved data points to the wrong constant | ordinal() was stored and constants were reordered | Store 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()andordinal()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,hashCodeandtoString. - Record accessors are named
x(), notgetX(). 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