foreach-ui logo
codeLanguages
account_treeDSA

Quick Actions

quizlock Random Quiz
trending_uplock Progress
  • 1
  • 2
  • 3
  • 4
  • 5
  • quiz
Java
  • Master enum declaration with fields, methods, and abstract methods
  • Learn to create and use custom annotations
  • Understand annotation processing at runtime with reflection

Enums and Annotations in Java

How do you represent a fixed set of choices? Days of the week, order statuses, card suits? Before Java 5, you used integer constants or strings—both error-prone. Enums give you type-safe constants.

And how do you add metadata that tools and frameworks can read? Annotations. They tell Spring which classes are services, JUnit which methods are tests, and the compiler to check your code.

Enumerations (Enums)

An enum is a special class representing a fixed set of constants. Unlike strings or integers, enums are type-safe—the compiler catches mistakes.

The Problem with Constants

Without enums, representing fixed choices typically looked like this:

// Approach 1: Integer constants (problematic)
public static final int STATUS_PENDING = 0;
public static final int STATUS_APPROVED = 1;
public static final int STATUS_REJECTED = 2;

void processOrder(int status) {
    if (status == STATUS_PENDING) { ... }
}

// Problems:
processOrder(999);  // Compiles! What does 999 mean?
processOrder(STATUS_PENDING + 1);  // Accidental arithmetic

// Approach 2: String constants (also problematic)
public static final String STATUS_PENDING = "pending";

void processOrder(String status) {
    if (status.equals(STATUS_PENDING)) { ... }
}

// Problems:
processOrder("pendingg");  // Typo compiles fine!

Basic Enum Declaration

Enums solve these problems:

public enum OrderStatus {
    PENDING,
    APPROVED,
    REJECTED,
    SHIPPED,
    DELIVERED
}

That's it - a type-safe set of constants. Usage:

OrderStatus status = OrderStatus.PENDING;

// Type-safe: only valid values allowed
void processOrder(OrderStatus status) {
    // This method can ONLY receive one of the five valid statuses
}

processOrder(OrderStatus.APPROVED);  // OK
processOrder("approved");            // Compile error!
processOrder(1);                     // Compile error!

Enums in Switch Statements

Enums work naturally with switch:

public String getStatusMessage(OrderStatus status) {
    switch (status) {
        case PENDING:
            return "Your order is being processed";
        case APPROVED:
            return "Your order has been approved";
        case REJECTED:
            return "Sorry, your order was rejected";
        case SHIPPED:
            return "Your order is on its way!";
        case DELIVERED:
            return "Your order has been delivered";
        default:
            throw new IllegalArgumentException("Unknown status");
    }
}

// Java 14+ enhanced switch
public String getStatusMessage(OrderStatus status) {
    return switch (status) {
        case PENDING -> "Your order is being processed";
        case APPROVED -> "Your order has been approved";
        case REJECTED -> "Sorry, your order was rejected";
        case SHIPPED -> "Your order is on its way!";
        case DELIVERED -> "Your order has been delivered";
    };
}

Built-in Enum Methods

Every enum automatically has useful methods:

OrderStatus status = OrderStatus.APPROVED;

// Get the name as a string
String name = status.name();          // "APPROVED"
String str = status.toString();       // "APPROVED" (by default, same as name())

// Get the position (0-based)
int position = status.ordinal();      // 1 (PENDING=0, APPROVED=1, ...)

// Get all values
OrderStatus[] allStatuses = OrderStatus.values();

// Convert string to enum
OrderStatus parsed = OrderStatus.valueOf("APPROVED");  // Returns APPROVED
OrderStatus invalid = OrderStatus.valueOf("UNKNOWN");  // IllegalArgumentException!

// Safe parsing
public static OrderStatus safeValueOf(String name) {
    try {
        return OrderStatus.valueOf(name);
    } catch (IllegalArgumentException e) {
        return null;  // or a default value
    }
}

Enums with Fields and Methods

Enums are classes - they can have fields, constructors, and methods:

public enum Planet {
    MERCURY(3.303e23, 2.4397e6),
    VENUS(4.869e24, 6.0518e6),
    EARTH(5.976e24, 6.37814e6),
    MARS(6.421e23, 3.3972e6),
    JUPITER(1.9e27, 7.1492e7),
    SATURN(5.688e26, 6.0268e7),
    URANUS(8.686e25, 2.5559e7),
    NEPTUNE(1.024e26, 2.4746e7);
    
    private final double mass;   // in kilograms
    private final double radius; // in meters
    private static final double G = 6.67300E-11;
    
    // Constructor (always private for enums)
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
    
    public double getMass() { return mass; }
    public double getRadius() { return radius; }
    
    // Calculated property
    public double surfaceGravity() {
        return G * mass / (radius * radius);
    }
    
    public double surfaceWeight(double earthWeight) {
        double mass = earthWeight / EARTH.surfaceGravity();
        return mass * surfaceGravity();
    }
}

// Usage
double earthWeight = 75.0;  // kg
for (Planet p : Planet.values()) {
    System.out.printf("Weight on %s: %.2f%n", p, p.surfaceWeight(earthWeight));
}

Enums Implementing Interfaces

Enums can implement interfaces, enabling polymorphism:

public interface Describable {
    String getDescription();
}

public enum Season implements Describable {
    SPRING {
        @Override
        public String getDescription() {
            return "Flowers bloom, temperatures rise";
        }
    },
    SUMMER {
        @Override
        public String getDescription() {
            return "Hot days, vacation time";
        }
    },
    AUTUMN {
        @Override
        public String getDescription() {
            return "Leaves fall, temperatures drop";
        }
    },
    WINTER {
        @Override
        public String getDescription() {
            return "Cold days, holiday season";
        }
    };
}

// Each constant can have its own implementation!

EnumSet and EnumMap

Java provides optimized collections for enums:

// EnumSet - highly efficient Set for enums
EnumSet<OrderStatus> activeStatuses = EnumSet.of(
    OrderStatus.PENDING, 
    OrderStatus.APPROVED, 
    OrderStatus.SHIPPED
);

EnumSet<OrderStatus> completedStatuses = EnumSet.complementOf(activeStatuses);
EnumSet<OrderStatus> allStatuses = EnumSet.allOf(OrderStatus.class);

// EnumMap - Map with enum keys, very efficient
EnumMap<OrderStatus, String> statusMessages = new EnumMap<>(OrderStatus.class);
statusMessages.put(OrderStatus.PENDING, "Processing...");
statusMessages.put(OrderStatus.APPROVED, "Ready to ship!");

Annotations

Annotations are metadata tags you can attach to code elements (classes, methods, fields, parameters). They don't directly affect code execution, but they can be read by:

  • The compiler (for warnings, errors)
  • Development tools (for code generation)
  • Frameworks at runtime (for configuration)

Built-in Annotations

Java provides several standard annotations:

@Override - Tells the compiler you intend to override a superclass method:

public class Dog extends Animal {
    @Override
    public void makeSound() {  // Compiler verifies this actually overrides something
        System.out.println("Bark!");
    }
    
    @Override
    public void makeSond() {  // Compile error! No such method to override (typo)
        System.out.println("Bark!");
    }
}

@Deprecated - Marks code as outdated:

@Deprecated
public void oldMethod() {
    // This method is deprecated
}

@Deprecated(since = "2.0", forRemoval = true)  // Java 9+
public void veryOldMethod() {
    // Will be removed in a future version
}

@SuppressWarnings - Tells compiler to ignore specific warnings:

@SuppressWarnings("unchecked")
public void processList(List list) {
    // Suppress unchecked cast warning
    List<String> strings = (List<String>) list;
}

@SuppressWarnings({"unchecked", "deprecation"})
public void oldCode() { ... }

@FunctionalInterface - Documents that an interface is functional:

@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
    // If you add another abstract method, compiler error!
}

Creating Custom Annotations

You can define your own annotations:

// Simple marker annotation
public @interface Audited {
}

// Annotation with elements
public @interface Author {
    String name();
    String date();
    int version() default 1;  // Default value
}

// Usage
@Audited
@Author(name = "John Doe", date = "2024-01-15")
public class ImportantClass {
    // ...
}

Annotation Elements and Defaults

public @interface TestInfo {
    // Required elements
    String author();
    String[] tags();
    
    // Optional elements (have defaults)
    int priority() default 0;
    String description() default "";
}

@TestInfo(
    author = "Jane",
    tags = {"unit", "fast"},
    priority = 1
    // description uses default ""
)
public void testSomething() { }

Meta-Annotations

Meta-annotations annotate other annotations:

@Retention - When the annotation is available:

@Retention(RetentionPolicy.SOURCE)   // Discarded by compiler
@Retention(RetentionPolicy.CLASS)    // In .class file, not at runtime (default)
@Retention(RetentionPolicy.RUNTIME)  // Available at runtime via reflection

@Target - Where the annotation can be used:

@Target(ElementType.TYPE)           // Classes, interfaces, enums
@Target(ElementType.METHOD)         // Methods
@Target(ElementType.FIELD)          // Fields
@Target(ElementType.PARAMETER)      // Method parameters
@Target({ElementType.TYPE, ElementType.METHOD})  // Multiple targets

@Documented - Include in Javadoc:

@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PublicAPI { }

Reading Annotations at Runtime

With RUNTIME retention, you can read annotations using reflection:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
    String name() default "";
}

public class MyTests {
    @Test(name = "User creation test")
    public void testCreateUser() { }
    
    @Test
    public void testDeleteUser() { }
    
    public void helperMethod() { }  // Not a test
}

// Test runner
public class TestRunner {
    public static void runTests(Class<?> testClass) throws Exception {
        for (Method method : testClass.getDeclaredMethods()) {
            if (method.isAnnotationPresent(Test.class)) {
                Test test = method.getAnnotation(Test.class);
                System.out.println("Running: " + 
                    (test.name().isEmpty() ? method.getName() : test.name()));
                method.invoke(testClass.getDeclaredConstructor().newInstance());
            }
        }
    }
}

Real-World Annotation Examples

Annotations are widely used in frameworks:

// Spring
@Controller
@RequestMapping("/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
}

// JPA/Hibernate
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false)
    private String name;
}

// JUnit
public class CalculatorTest {
    @BeforeEach
    void setup() { }
    
    @Test
    @DisplayName("Adding two numbers")
    void testAdd() { }
}

Enums give you type-safe constants with fields and methods. Use them instead of integers or strings. Annotations add metadata that tools and frameworks read—@Override verifies method overrides, @Deprecated marks old code, custom annotations configure Spring and JPA. @Retention determines when annotations are available, @Target specifies where they're allowed.

© 2026 forEach. All rights reserved.

Privacy Policy•Terms of Service