- 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.
