Senior 5 min · March 05, 2026

Java Enums Explained — Fields, Methods, and Real-World Patterns

Java Enums go far beyond simple constants.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Enums define a fixed set of named constants as a type-safe alternative to int constants
  • Each constant is a singleton instance — can hold fields, methods, and implement interfaces
  • Use name() or a dedicated code field for persistence, never ordinal() — it breaks on reorder
  • Switch expressions over enums with no default give compiler-enforced exhaustiveness
  • EnumSet and EnumMap are purpose-built, high-performance collections for enum keys
Plain-English First

Imagine a traffic light. It can only ever be RED, YELLOW, or GREEN — nothing else. You wouldn't want someone accidentally setting it to PURPLE or 42. A Java enum is exactly that: a fixed, named list of things where the set of valid options is known upfront and never changes. Think of it as a locked menu at a restaurant — you can only order what's on it.

Most Java developers discover enums, use them to replace a bunch of int constants, and stop there. That's like buying a Swiss Army knife and only ever using it to open bottles. Enums in Java are fully-fledged classes — they can hold fields, implement interfaces, define abstract methods per constant, and plug cleanly into switch expressions. Teams that use them well write code that's safer, more readable, and almost self-documenting.

Before enums arrived in Java 5, developers used public static final int constants to represent fixed sets of values. The problem? Nothing stopped you passing the wrong int to a method that expected a specific constant. The compiler had no way to catch it. Enums solve this by giving each constant a real type, so the compiler enforces correctness at compile time — not at 2 AM when production is on fire.

By the end of this article you'll understand why enums exist, how to make them carry data and behaviour (not just names), how they interact with switch expressions in modern Java, and the subtle traps that catch even experienced developers off-guard. You'll also have a handful of real-world patterns you can drop straight into your next project.

Why Enums Beat Static Constants — and How to Define Them Right

The classic anti-pattern looks like this: three constants — int ORDER_PENDING = 0, int ORDER_SHIPPED = 1, int ORDER_DELIVERED = 2 — scattered in some utility class. Now every method that receives an order status takes an int, and nothing prevents a caller from passing 99. The compiler shrugs.

An enum eliminates that entire class of bug. Once you declare enum OrderStatus { PENDING, SHIPPED, DELIVERED }, any method that accepts an OrderStatus will refuse to compile if you pass anything that isn't one of those three values. The type system works for you.

But here's what most tutorials skip: every enum constant is secretly an instance of the enum class itself. That means OrderStatus.PENDING is an object. It has the methods name(), ordinal(), and toString() built in. name() returns the exact declared name as a String. ordinal() returns its zero-based position. These are useful, but they also create a gotcha we'll cover later — never rely on ordinal() for business logic.

The values() static method returns an array of all constants in declaration order, and valueOf(String) looks up a constant by name. These two methods are generated automatically by the compiler for every enum you write.

OrderStatusDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class OrderStatusDemo {

    // Declaring an enum — each constant is an instance of OrderStatus
    enum OrderStatus {
        PENDING,
        SHIPPED,
        DELIVERED,
        CANCELLED
    }

    public static void printStatusInfo(OrderStatus status) {
        // name() returns the exact String used in the declaration
        System.out.println("Status name   : " + status.name());

        // ordinal() returns the zero-based position in the declaration order
        System.out.println("Status ordinal: " + status.ordinal());

        // toString() defaults to name() unless you override it
        System.out.println("Status string : " + status.toString());
    }

    public static void main(String[] args) {
        printStatusInfo(OrderStatus.SHIPPED);

        System.out.println("\n--- All statuses via values() ---");
        // values() is auto-generated — iterates in declaration order
        for (OrderStatus s : OrderStatus.values()) {
            System.out.printf("%-10s -> ordinal %d%n", s.name(), s.ordinal());
        }

        System.out.println("\n--- Lookup by name via valueOf() ---");
        // valueOf() throws IllegalArgumentException if the name doesn't match exactly
        OrderStatus looked = OrderStatus.valueOf("CANCELLED");
        System.out.println("Found: " + looked);
    }
}
Output
Status name : SHIPPED
Status ordinal: 1
Status string : SHIPPED
--- All statuses via values() ---
PENDING -> ordinal 0
SHIPPED -> ordinal 1
DELIVERED -> ordinal 2
CANCELLED -> ordinal 3
--- Lookup by name via valueOf() ---
Found: CANCELLED
Watch Out: ordinal() is fragile
Never store or compare enum values by their ordinal() in a database or file. If you ever reorder, add, or remove a constant, every stored ordinal silently points to the wrong value. Store name() or a dedicated code field instead — we'll add one in the next section.
Production Insight
The compile-time safety of enums eliminates entire categories of bugs.
But the auto-generated methods like ordinal() are a trap in production.
Rule: trust the type system, but distrust default serialization choices.
Key Takeaway
Enums give you compile-time safety that int constants never can.
But name() and ordinal() are auto-generated — use name() for persistence, never ordinal().

Giving Enums Fields and Methods — Where the Real Power Lives

Here's the moment most developers level up with enums: realising each constant can carry its own data. Say you're building an e-commerce platform and every order status needs a human-readable label and a boolean that says whether the order can still be cancelled. You could maintain three separate maps to track all that — or you could make the enum hold it directly.

To add fields, you declare them in the enum body, write a constructor, and pass values to each constant in parentheses — just like calling a constructor. The constructor must be private (or package-private); enums can't be instantiated from outside.

Methods work identically to methods on a regular class. You can add instance methods that use the constant's own fields, static methods that operate across all constants, and even override toString() so that logging and debugging produce friendly output instead of the raw constant name.

This pattern — an enum that owns its own data — is far more maintainable than parallel arrays or maps. When a new status is added, you add it in exactly one place, and the compiler immediately tells you every switch statement that needs updating.

RichOrderStatus.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class RichOrderStatus {

    enum OrderStatus {
        // Each constant calls the private constructor with its specific values
        PENDING("Awaiting processing", true),
        SHIPPED("On the way to you", false),
        DELIVERED("Order complete", false),
        CANCELLED("Order cancelled", false);

        // Fields stored per-constant — declared final because they never change
        private final String displayLabel;
        private final boolean cancellable;

        // Constructor MUST be private — enums control their own instantiation
        private OrderStatus(String displayLabel, boolean cancellable) {
            this.displayLabel = displayLabel;
            this.cancellable = cancellable;
        }

        // Accessor for the human-readable label
        public String getDisplayLabel() {
            return displayLabel;
        }

        // Accessor for the business rule
        public boolean isCancellable() {
            return cancellable;
        }

        // Override toString so logs are immediately readable
        @Override
        public String toString() {
            return name() + " (" + displayLabel + ")";
        }
    }

    // A service method that uses the enum's built-in behaviour — no if-else chain needed
    public static void attemptCancellation(OrderStatus currentStatus) {
        if (currentStatus.isCancellable()) {
            System.out.println("Cancelling order. Status was: " + currentStatus);
        } else {
            System.out.println("Cannot cancel. Current status: " + currentStatus);
        }
    }

    public static void main(String[] args) {
        // Demonstrate rich data attached to each constant
        for (OrderStatus status : OrderStatus.values()) {
            System.out.printf("%-10s | %-25s | Cancellable: %s%n",
                    status.name(),
                    status.getDisplayLabel(),
                    status.isCancellable());
        }

        System.out.println();
        attemptCancellation(OrderStatus.PENDING);    // still cancellable
        attemptCancellation(OrderStatus.SHIPPED);    // already shipped — no
    }
}
Output
PENDING | Awaiting processing | Cancellable: true
SHIPPED | On the way to you | Cancellable: false
DELIVERED | Order complete | Cancellable: false
CANCELLED | Order cancelled | Cancellable: false
Cancelling order. Status was: PENDING (Awaiting processing)
Cannot cancel. Current status: SHIPPED (On the way to you)
Pro Tip: Encode business rules inside the enum
Notice how isCancellable() lives on the enum itself — not in a service class or an if-else chain scattered across the codebase. When the rule changes (say, PENDING AND SHIPPED become cancellable within 1 hour), you change it in exactly one place. This is the single-responsibility principle applied to enums.
Production Insight
Encapsulating business logic inside the enum prevents scattered changes.
But remember: constructors are called once per constant at class loading time.
If a constructor throws, the entire class fails to load — a common production trap.
Key Takeaway
Add fields and methods to enums to keep related data and logic together.
Constructors run at class loading — keep them simple to avoid startup failures.

Enums in Switch Expressions — Modern Java's Killer Combo

Switch statements and enums were always a natural pair, but Java 14+ switch expressions make this combination genuinely elegant. The compiler can now warn you — or in some tools, flat-out refuse to compile — if your switch doesn't cover every enum constant. That's exhaustiveness checking, and it's a huge safety net.

The arrow syntax (->) eliminates the infamous fall-through bug. Each arm returns a value directly, so you can assign the result of a switch expression to a variable. No more break statements, no more accidentally falling through to the next case at 2 AM.

Combine this with a sealed interface or record in modern Java and you get pattern matching that's both type-safe and readable. But even without those features, the enum + switch expression pairing is one of the cleanest patterns in the Java toolbox.

One more thing worth knowing: EnumSet and EnumMap from java.util are purpose-built collections for enums. They're dramatically faster than HashSet and HashMap when your keys or elements are enum constants. If you're ever storing a subset of an enum's constants, reach for EnumSet first.

ShippingCostCalculator.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;

public class ShippingCostCalculator {

    enum DeliveryTier {
        STANDARD,
        EXPRESS,
        OVERNIGHT,
        SAME_DAY
    }

    // Switch EXPRESSION (Java 14+) — returns a value, no fall-through possible
    public static double calculateShippingCost(DeliveryTier tier, double orderValueGbp) {
        // Each arrow arm is an expression — result assigned directly to shippingCost
        double baseRate = switch (tier) {
            case STANDARD  -> 2.99;   // cheapest, slowest
            case EXPRESS   -> 5.99;
            case OVERNIGHT -> 9.99;
            case SAME_DAY  -> 14.99;  // premium option
            // No 'default' needed — compiler verifies all enum constants are covered
        };

        // Free standard shipping on orders over £50 — business rule in one place
        if (tier == DeliveryTier.STANDARD && orderValueGbp >= 50.0) {
            return 0.0;
        }
        return baseRate;
    }

    public static void main(String[] args) {
        // EnumMap — keys are enum constants, internally uses a simple array (very fast)
        Map<DeliveryTier, Double> priceList = new EnumMap<>(DeliveryTier.class);
        for (DeliveryTier tier : DeliveryTier.values()) {
            priceList.put(tier, calculateShippingCost(tier, 30.0)); // £30 order
        }

        System.out.println("--- Shipping costs for a £30.00 order ---");
        priceList.forEach((tier, cost) ->
                System.out.printf("%-10s : £%.2f%n", tier, cost));

        System.out.println("\n--- Standard shipping on a £55.00 order ---");
        double freeShipping = calculateShippingCost(DeliveryTier.STANDARD, 55.0);
        System.out.printf("Standard cost  : £%.2f (free over £50)%n", freeShipping);

        // EnumSet — a fast, compact set of enum constants
        Set<DeliveryTier> premiumTiers = EnumSet.of(DeliveryTier.OVERNIGHT, DeliveryTier.SAME_DAY);
        System.out.println("\nPremium tiers  : " + premiumTiers);
        System.out.println("EXPRESS premium? " + premiumTiers.contains(DeliveryTier.EXPRESS));
    }
}
Output
--- Shipping costs for a £30.00 order ---
STANDARD : £2.99
EXPRESS : £5.99
OVERNIGHT : £9.99
SAME_DAY : £14.99
--- Standard shipping on a £55.00 order ---
Standard cost : £0.00 (free over £50)
Premium tiers : [OVERNIGHT, SAME_DAY]
EXPRESS premium? false
Interview Gold: Why no default in an enum switch expression?
When you use a switch expression over an enum (without a default arm), the compiler verifies every constant is handled. If you add a new constant later, the code won't compile until you handle it. A default silently swallows new constants — you lose that safety net. Omitting default is a deliberate, defensive choice.
Production Insight
Switch expressions with enums and no default give compile-time exhaustiveness.
Production mistake: adding a default arm 'just in case' defeats that safety.
If you must handle unknown values (e.g., future-proofing), use a final 'else' outside the switch.
Key Takeaway
Switch expressions + enums + no default = compiler-enforced safety net.
EnumSet and EnumMap are your go-to collections for enum data.

Abstract Methods Per Constant — When Each Value Needs Its Own Behaviour

Here's the advanced move that surprises most developers: you can declare an abstract method on an enum and force each constant to provide its own implementation. This is the Strategy pattern built directly into the type itself.

The use case is when each constant doesn't just hold different data — it actually does something differently. Think of payment methods: a CREDIT_CARD payment applies a processing fee, while a BANK_TRANSFER doesn't. You could put this logic in a switch, but that means every time you add a payment method you have to find and update the switch. With an abstract method, forgetting to implement it on a new constant is a compile error — the build breaks loudly before your bug ever reaches production.

This pattern replaces entire Strategy hierarchies in many cases. Instead of a PaymentProcessor interface with CreditCardProcessor, BankTransferProcessor, and PayPalProcessor classes all wired together with a factory, you put the behaviour right on the enum constant. Fewer files, fewer moving parts, same type safety.

PaymentMethodDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class PaymentMethodDemo {

    enum PaymentMethod {

        // Each constant implements the abstract method with its own logic
        CREDIT_CARD {
            @Override
            public double calculateFee(double orderAmount) {
                // 1.5% processing fee for credit cards
                return orderAmount * 0.015;
            }

            @Override
            public String getDescription() {
                return "Credit Card (1.5% fee)";
            }
        },

        BANK_TRANSFER {
            @Override
            public double calculateFee(double orderAmount) {
                // Flat £0.30 fee for bank transfers, regardless of order size
                return 0.30;
            }

            @Override
            public String getDescription() {
                return "Bank Transfer (£0.30 flat fee)";
            }
        },

        PAYPAL {
            @Override
            public double calculateFee(double orderAmount) {
                // PayPal: 2.9% plus a fixed £0.30
                return (orderAmount * 0.029) + 0.30;
            }

            @Override
            public String getDescription() {
                return "PayPal (2.9% + £0.30)";
            }
        };

        // Abstract methods — every constant MUST implement these or code won't compile
        public abstract double calculateFee(double orderAmount);
        public abstract String getDescription();

        // Concrete method shared by ALL constants — no override needed
        public double totalCharge(double orderAmount) {
            return orderAmount + calculateFee(orderAmount);
        }
    }

    public static void main(String[] args) {
        double orderAmount = 100.00;

        System.out.printf("Order value: £%.2f%n%n", orderAmount);
        System.out.printf("%-35s | %-8s | %-12s%n", "Method", "Fee", "Total");
        System.out.println("-".repeat(60));

        for (PaymentMethod method : PaymentMethod.values()) {
            double fee   = method.calculateFee(orderAmount);
            double total = method.totalCharge(orderAmount);
            System.out.printf("%-35s | £%-7.2f | £%-10.2f%n",
                    method.getDescription(), fee, total);
        }
    }
}
Output
Order value: £100.00
Method | Fee | Total
------------------------------------------------------------
Credit Card (1.5% fee) | £1.50 | £101.50
Bank Transfer (£0.30 flat fee) | £0.30 | £100.30
PayPal (2.9% + £0.30) | £3.20 | £103.20
Pro Tip: Abstract enum methods as a lightweight Strategy pattern
Before you reach for a Strategy interface with multiple implementation classes wired through a factory, ask whether a constant-specific abstract method on an enum is enough. It often is — and it's self-contained, easier to read, and impossible to forget to implement for a new constant.
Production Insight
Abstract methods on enum constants force you to implement behaviour at definition time.
The compiler won't let you add a new constant without implementing every abstract method.
This pattern is ideal for small, fixed sets of behaviours — don't use it for dynamic rules.
Key Takeaway
Abstract enum methods = compile-time enforced Strategy pattern.
Use when each constant has unique behaviour — avoid for large or changing rule sets.

Enums and Interfaces — Marrying Flexibility with Type Safety

Did you know enums can implement interfaces? This is a powerful trick that combines the singleton guarantee of enums with the polymorphic flexibility of interfaces. For example, you can define a PaymentMethod interface with a calculateFee method, then have your PaymentMethodEnum implement it. This lets you write code that works against the interface (great for dependency injection) while still enjoying enum features like values() and valueOf().

Another pattern is using an enum to implement a well-known interface like Runnable or Comparator. Need a small, fixed set of comparators? Define them as an enum implementing Comparator<T>. Each constant provides its own compare() logic. Clean, type-safe, and you get serialization for free (enums are inherently serializable).

One subtlety: when an enum implements an interface, you can't use constant-specific methods unless the interface declares them. The interface must define methods that every constant will implement. This is actually a strength — it enforces a uniform contract across all constants.

EnumWithInterface.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.Comparator;

// An enum implementing Comparator for a simple string ordering
enum StringComparator implements Comparator<String> {
    NATURAL {
        @Override
        public int compare(String a, String b) {
            return a.compareTo(b);
        }
    },
    REVERSE {
        @Override
        public int compare(String a, String b) {
            return b.compareTo(a);
        }
    },
    LENGTH {
        @Override
        public int compare(String a, String b) {
            return Integer.compare(a.length(), b.length());
        }
    }
}

public class EnumWithInterface {
    public static void main(String[] args) {
        String[] words = {"kiwi", "apple", "banana", "cherry"};

        for (StringComparator comp : StringComparator.values()) {
            System.out.println("Sorting with " + comp.name() + ":");
            java.util.Arrays.sort(words, comp);
            System.out.println(java.util.Arrays.toString(words));
        }
    }
}
Output
Sorting with NATURAL:
[apple, banana, cherry, kiwi]
Sorting with REVERSE:
[kiwi, cherry, banana, apple]
Sorting with LENGTH:
[kiwi, apple, cherry, banana]
Why implement an interface with an enum?
You get both worlds: the singleton, type-safety, and built-in methods of enums plus the polymorphism of interfaces. This pattern also makes enums injectable where interfaces are expected, e.g., in Spring beans or strategy consumers.
Production Insight
Enums implementing interfaces are great for fixed sets of strategies.
But remember: the interface must declare every method each constant needs.
If you need constant-specific methods beyond the interface, consider abstract class approach instead.
Key Takeaway
Enums can implement interfaces — combine singleton safety with polymorphic contracts.
Ideal for small sets of comparators, strategies, or predicates.
● Production incidentPOST-MORTEMseverity: high

The Case of the Vanishing Order Statuses

Symptom
Orders randomly showing wrong statuses after a deployment that added a new status constant.
Assumption
ordinal() is stable because it's based on declaration order — surely adding a constant at the end won't break existing entries.
Root cause
The new constant was inserted in the middle of the enum declaration (not at the end). The ordinal() of every constant after the insertion point incremented by one, corrupting all persisted values.
Fix
Migrate stored data from ordinal() to name() (string) or introduce a dedicated stable code field. Then update the enum to use name() for serialization and add a migration script for existing records.
Key lesson
  • Never persist ordinal() — it's tied to declaration order and changes silently.
  • Always use name() or an explicit code field for database or file storage.
  • If you must store a numeric code, define it explicitly in the enum constructor and expose a method — don't rely on ordinal().
Production debug guideQuick symptom-to-action guide for the most common enum failures.3 entries
Symptom · 01
valueOf() throws IllegalArgumentException when parsing external data
Fix
Wrap valueOf() in a helper with try-catch returning Optional<Enum>. Use a case-insensitive lookup (iterate values() and match ignoring case) if input is user-provided.
Symptom · 02
New enum constant added but switch statement doesn't handle it — no compile error
Fix
Enable 'incomplete-switch' as error in your IDE or add -Xlint:switch to javac. Better yet, use switch expressions without default — then the compiler forces you to handle every constant.
Symptom · 03
Enum constant with field returns null unexpectedly
Fix
Check that the constructor assigns the field. If the constructor is missing or parameter order is wrong, fields may be left at default values. Ensure all constants pass the correct arguments.
★ Cheat Sheet: Enum Quick DebugCommon enum problems and the exact commands or code changes to diagnose and fix them fast.
Corrupted enum data due to ordinal() persistency
Immediate action
Stop deployment, roll back if possible.
Commands
SELECT * FROM orders WHERE status = 2; to check ordinal values in DB
Add method to enum: public int getCode() { return code; } and expose getCode() for new writes.
Fix now
Write a data migration script updating old records to match the new ordinal mapping.
Switch on enum compiles but missing case not caught+
Immediate action
Convert the switch statement to a switch expression.
Commands
javac -Xlint:all YourFile.java 2>&1 | grep -i enum
Add @SuppressWarnings("incomplete-switch")? No, remove default and handle all constants.
Fix now
Refactor switch to expression: int result = switch(enumVal) { case A -> 1; case B -> 2; ... };
Enum vs Static int Constants: A Side-by-Side Comparison
Feature / AspectStatic int Constants (old way)Java Enum (modern way)
Type safetyNone — any int accepted by compilerFull — only declared constants compile
Carrying data (fields)Requires separate parallel arrays/mapsFields declared directly on the enum
Behaviour per valueGiant switch or if-else chain elsewhereAbstract method per constant on the enum itself
Iteration over all valuesManual array, easy to forget new entriesvalues() auto-generated, always complete
Switch exhaustivenessCompiler can't check coverageCompiler warns/errors if constants are missing
Serialisation safetyStore the int — renumbering breaks everythingStore name() — refactor-safe
Collections supportGeneric HashMap/HashSetEnumMap / EnumSet — faster, less memory
Implements interfacesNot applicableYes — enums can implement interfaces
Singleton guaranteeMust enforce manuallyEach constant is a JVM-guaranteed singleton

Key takeaways

1
Every enum constant is a singleton instance of the enum class
it can hold fields and methods, not just a name.
2
Never persist ordinal()
it shifts silently when constants are reordered. Store name() or a dedicated stable field instead.
3
Omitting default in a switch expression over an enum is a deliberate safety choice
the compiler enforces exhaustiveness, catching missing cases when you add new constants.
4
EnumSet and EnumMap are the right collections for enum keys/values
they're backed by bit vectors and arrays internally, making them faster and more memory-efficient than their generic counterparts.
5
Enums can implement interfaces and have abstract methods
use these patterns to create self-contained, compile-time enforced behaviour.

Common mistakes to avoid

4 patterns
×

Relying on ordinal() for persistence

Symptom
After adding or reordering constants, every stored ordinal silently maps to the wrong constant. Orders show incorrect statuses without any error or log.
Fix
Use name() as the persistent representation. If you need a stable numeric code, add an explicit code field in the constructor and expose a getter. Never use ordinal() for anything that persists or is sent over the wire.
×

Using valueOf() without a try-catch

Symptom
valueOf(String) throws an unchecked IllegalArgumentException if the input doesn't exactly match a constant name. Production crashes when external data is case-insensitive or has leading/trailing whitespace.
Fix
Wrap valueOf() in a static helper method that returns Optional<Enum> or a default value. For case-insensitive lookups, iterate values() and compare using equalsIgnoreCase.
×

Adding a default arm to an enum switch expression

Symptom
When a new constant is added, the default arm silently handles it, introducing a logic bug that only surfaces at runtime. The compiler loses its ability to detect incomplete coverage.
Fix
Never add a default arm to a switch expression over an enum. Let the compiler enforce exhaustiveness. If you truly need to handle unknown values, place a return/break after the switch expression (outside).
×

Assuming enum constants are created lazily

Symptom
Complex constructor logic (e.g., database calls) runs at class loading time. A single failure prevents the entire enum from loading, crashing the application on startup.
Fix
Keep constructors simple and side-effect-free. Move heavy initialization to a static block or use a lazy initialization pattern within each constant.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Can an enum implement an interface in Java? Walk me through a realistic ...
Q02SENIOR
What's the difference between name() and toString() on an enum constant,...
Q03SENIOR
Why is it dangerous to omit a default case in a switch statement on an e...
Q01 of 03SENIOR

Can an enum implement an interface in Java? Walk me through a realistic scenario where you'd actually want to do that.

ANSWER
Yes, enums can implement one or more interfaces. A realistic scenario is implementing a Comparator as an enum. For example, you can define StringComparator.NATURAL, StringComparator.REVERSE, and StringComparator.LENGTH as enum constants, each with its own compare() method. This gives you a fixed set of comparators that are type-safe, serializable, and easy to use with Collections.sort(). Another scenario: implementing a PaymentStrategy interface to encapsulate fee calculations per payment method.
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
Can a Java enum extend another class?
02
Is it safe to compare enum values with == instead of equals()?
03
When should I use an enum versus a sealed interface in modern Java?
04
Can an enum have instance variables that are not final?
🔥

That's Advanced Java. Mark it forged?

5 min read · try the examples if you haven't

Previous
Annotations in Java
4 / 28 · Advanced Java
Next
Inner Classes in Java