← Back to Home

Java Enum Guide

Java · enum, enum class semantics

Complete Java Enum Guide

Java enums are more than simple constant collections—they are full classes with fields, constructors, and methods. Since their introduction in Java 5, enums have become the standard tool for expressing fixed value sets and implementing strategy patterns. This guide covers everything from basic definitions to enterprise-level applications.

This guide covers enum constructors, value object encapsulation, string mapping, switch expressions, and Jackson integration.

Java Enum Fundamentals

Java 5 introduced enums, combining type safety with object-oriented features: enums can have fields, constructors, methods, and can implement interfaces. This makes enums ideal for defining business constants, configuration-driven patterns, and strategy distribution.

Core Template

public enum OrderStatus {
    PENDING("pending", "Pending Payment"),
    PAID("paid", "Paid"),
    REFUNDED("refunded", "Refunded");

    private final String code;
    private final String label;

    OrderStatus(String code, String label) {
        this.code = code;
        this.label = label;
    }

    public String getCode() { return code; }
    public String getLabel() { return label; }

    public static OrderStatus fromCode(String code) {
        for (OrderStatus status : values()) {
            if (status.code.equals(code)) {
                return status;
            }
        }
        throw new IllegalArgumentException("No enum constant for code " + code);
    }
}

Includes standard patterns for enum constructors, string conversion, and value encapsulation with defensive techniques.

Common Patterns: From Simple Enums to Strategy Enums

Practical patterns for common needs like "java enum with constructor", "java enum methods", "java enum string value", and "java enum ordinal".

Strategy Enum

Use abstract methods, function interfaces, or lambdas to replace traditional switches, ensuring compiler warnings when new enum values are added.

public enum DiscountStrategy {
    NONE(order -> order),
    BLACK_FRIDAY(order -> order.applyRate(0.5)),
    VIP(order -> order.applyRate(0.8));

    private final UnaryOperator operator;

    DiscountStrategy(UnaryOperator operator) {
        this.operator = operator;
    }

    public Order apply(Order order) {
        return operator.apply(order);
    }
}

Covers common patterns for encapsulating behavior in business code and integrating JSON serialization.

Enum and Jackson Serialization

Map `enum` to string values instead of names for API compatibility.

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PaymentMethod {
    CREDIT_CARD("credit_card", "Credit Card"),
    PAYPAL("paypal", "PayPal");

    private final String code;
    private final String label;

    @JsonValue
    public String getCode() { return code; }
}

Further reading: enum to string java, java enum tostring, system text json enum string.

Enum Switch & Pattern Matching

Common questions include "java switch enum", "java switch case enum", "switch case enum java", and "switch on enum java". Java 17+ pattern matching simplifies control flow and reduces the risk of forgetting to handle enum values.

static String render(OrderStatus status) {
    return switch (status) {
        case PENDING -> "Awaiting payment";
        case PAID -> "Completed";
        case REFUNDED -> "Refunded";
    };
}

Maintaining switch exhaustiveness allows the compiler to warn when new enum values are added, preventing logic omissions.

  • ✔ Log and throw exceptions when using `default` to avoid silent failures.
  • ✔ Customize conversion via `@InitBinder` when integrating with Spring MVC / Spring Boot.
  • ✔ Provide fallback strategies to handle `IllegalArgumentException: No enum constant`.

Java Enum Playbook

Java Enum Cheat Sheet

  • • Complete examples: constructors, custom methods, valueOf, switch expressions
  • • Jackson/Gson serialization configuration and custom deserializers
  • • Naming conventions: follow Java enum constant uppercase conventions

Team Code Review Checklist

✓ Design Standards

  • • Use UPPER_SNAKE_CASE for enum constants, separated by underscores (e.g., ORDER_PENDING)
  • • Use private final fields with getters for business values
  • • Constructors must be private (this is mandatory)
  • • Provide static factory methods for conversion from strings or codes

✓ Serialization

  • • Jackson serializes to enum names by default, use @JsonValue to specify custom fields
  • • Implement custom deserializers for case-insensitive handling or aliases
  • • Use @Enumerated(EnumType.STRING) in JPA entities to avoid ordinal changes
  • • Register converters via StringToEnumConverterFactory in Spring MVC

✓ Completeness Checks

  • • Switch expressions must be exhaustive or provide a default branch
  • • Use SonarLint/Checkstyle to detect uncovered enum values
  • • Search globally for all switch usages when adding new enum values
  • • Avoid changing enum ordinal order in public APIs

Naming and Design Anti-patterns

❌ Anti-pattern: Using ordinal() for business logic

public enum Status {
    PENDING, PROCESSING, COMPLETED
}

// Dangerous! Inserting new values changes ordinal
if (status.ordinal() == 2) { ... }

Issue: ordinal is an internal index, inserting new values breaks logic

✅ Correct: Use explicit business values

public enum Status {
    PENDING("pending", 0),
    PROCESSING("processing", 1),
    COMPLETED("completed", 2);
    
    private final String code;
    private final int value;
    
    Status(String code, int value) {
        this.code = code;
        this.value = value;
    }
}

❌ Anti-pattern: Using valueOf directly with user input

// Throws IllegalArgumentException
Status status = Status.valueOf(userInput);

✅ Correct: Provide safe conversion methods

public static Optional<Status> fromCode(String code) {
    return Arrays.stream(values())
        .filter(s -> s.code.equalsIgnoreCase(code))
        .findFirst();
}

❌ Anti-pattern: Switch missing default and not exhaustive

String message = switch(status) {
    case PENDING -> "Pending";
    case PROCESSING -> "Processing";
    // Forgot to handle COMPLETED!
};

✅ Correct: Handle all cases or provide default

String message = switch(status) {
    case PENDING -> "Pending";
    case PROCESSING -> "Processing";
    case COMPLETED -> "Completed";
}; // Java compiler checks exhaustiveness

Jackson Serialization Configuration

Configure enum serialization in Spring Boot:

// 1. Enum definition
public enum OrderStatus {
    PENDING("pending", "Pending"),
    PROCESSING("processing", "Processing"),
    COMPLETED("completed", "Completed");
    
    private final String code;
    private final String description;
    
    OrderStatus(String code, String description) {
        this.code = code;
        this.description = description;
    }
    
    @JsonValue  // Use code field for serialization
    public String getCode() {
        return code;
    }
    
    public String getDescription() {
        return description;
    }
    
    // Deserialization factory method
    @JsonCreator
    public static OrderStatus fromCode(String code) {
        return Arrays.stream(values())
            .filter(s -> s.code.equals(code))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException(
                "Unknown status code: " + code));
    }
}

// 2. Global configuration (application.yml)
spring:
  jackson:
    serialization:
      write-enums-using-to-string: false  # Use @JsonValue

Tip: @JsonValue makes JSON use the code field, @JsonCreator handles deserialization

JUnit Test Template

Ensure enum completeness and consistency:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import static org.assertj.core.api.Assertions.*;

class OrderStatusTest {
    
    @Test
    void allEnumsShouldHaveUniqueCode() {
        // Ensure code field has no duplicates
        Set<String> codes = Arrays.stream(OrderStatus.values())
            .map(OrderStatus::getCode)
            .collect(Collectors.toSet());
        assertThat(codes).hasSize(OrderStatus.values().length);
    }
    
    @Test
    void shouldSerializeAndDeserialize() throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        
        for (OrderStatus status : OrderStatus.values()) {
            String json = mapper.writeValueAsString(status);
            OrderStatus deserialized = mapper.readValue(
                json, OrderStatus.class);
            assertThat(deserialized).isEqualTo(status);
        }
    }
    
    @ParameterizedTest
    @EnumSource(OrderStatus.class)
    void everyStatusShouldBeHandledInBusinessLogic(OrderStatus status) {
        // Ensure business logic handles all statuses
        String message = OrderService.getStatusMessage(status);
        assertThat(message).isNotNull().isNotEmpty();
    }
    
    @Test
    void shouldMatchFrontendContract() {
        // Ensure values match frontend contract
        Set<String> expectedCodes = Set.of(
            "pending", "processing", "completed");
        Set<String> actualCodes = Arrays.stream(OrderStatus.values())
            .map(OrderStatus::getCode)
            .collect(Collectors.toSet());
        assertThat(actualCodes).isEqualTo(expectedCodes);
    }
}

Tip: @EnumSource automatically runs tests for each enum value

Spring Boot Integration Guide

Using Enums in JPA Entities

@Entity
public class Order {
    @Id
    private Long id;
    
    @Enumerated(EnumType.STRING)  // Store enum name, not ordinal
    @Column(name = "status", nullable = false)
    private OrderStatus status;
}

Controller Parameter Binding

@RestController
public class OrderController {
    
    @GetMapping("/orders")
    public List<Order> findOrders(
        @RequestParam OrderStatus status) {  // Auto-conversion
        return orderService.findByStatus(status);
    }
    
    // Custom converter (optional)
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(OrderStatus.class,
            new PropertyEditorSupport() {
                @Override
                public void setAsText(String text) {
                    setValue(OrderStatus.fromCode(text)
                        .orElseThrow());
                }
            });
    }
}

OpenAPI Documentation Configuration

@Schema(description = "Order status", 
        enumAsRef = true,
        example = "pending")
public enum OrderStatus {
    @Schema(description = "Pending")
    PENDING("pending"),
    @Schema(description = "Processing")
    PROCESSING("processing"),
    @Schema(description = "Completed")
    COMPLETED("completed");
}

Tip: Use EnumType.STRING to ensure database compatibility

Java Enum FAQ

What is enum in Java?

An enum is a special class that defines a fixed set of constants. Each enum member is an instance of the enum type and can have fields, methods, and even implement interfaces.

What to do when java enum valueOf throws an error?

`valueOf` only accepts enum names. It's recommended to wrap it with a custom `fromCode` method that normalizes strings and returns business-friendly error messages in exceptions.

How to convert a string to an enum?

Besides `valueOf`, you can maintain a `Map` or use `Enum.valueOf(enumClass, name)` with Optional for null-safe handling.

Can enums inherit?

Enums implicitly extend `java.lang.Enum` and cannot extend other classes, but they can implement interfaces to abstract behavior.