env.dev

Constants in Java

static final, enums, and immutable collections

static final: Java's Constants

Java defines constants using the static final modifier combination. A static final field belongs to the class (not to instances), and its value cannot be changed after initialization. By convention, constant names use SCREAMING_SNAKE_CASE.

java
public class AppConfig {
    public static final int MAX_RETRIES = 3;
    public static final String API_BASE_URL = "https://api.example.com/v1";
    public static final long TIMEOUT_MS = 30_000L;
    public static final double TAX_RATE = 0.0825;
}

The final keyword prevents reassignment but, like JavaScript's const, does not make objects immutable. A static final List can still be modified through add() and remove(). To make collections truly constant, use immutable factory methods or unmodifiable wrappers.

Compile-Time Constants

Java distinguishes between compile-time constants and regular final fields. A compile-time constant is a static final field of a primitive type or String initialized with a constant expression. The compiler inlines these values at every use site, which means changing the value requires recompiling all dependent classes.

java
// Compile-time constant — inlined by javac
public static final int MAX_SIZE = 1024;
public static final String VERSION = "2.0";

// NOT a compile-time constant — computed at class load
public static final long STARTUP_TIME = System.currentTimeMillis();
public static final String HOME = System.getenv("HOME");

This inlining behavior has an important consequence: if you change a compile-time constant in a library, all consuming code must be recompiled to pick up the new value. This is why some developers prefer static methods or enum values for constants that might change across library versions.

Enum Constants

Java enums are full-featured classes that can have fields, methods, constructors, and implement interfaces. They are the idiomatic way to define a fixed set of related constants with behavior. Every enum instance is a singleton, guaranteed to be created once during class loading.

java
public enum HttpStatus {
    OK(200, "OK"),
    CREATED(201, "Created"),
    BAD_REQUEST(400, "Bad Request"),
    NOT_FOUND(404, "Not Found"),
    INTERNAL_SERVER_ERROR(500, "Internal Server Error");

    private final int code;
    private final String reason;

    HttpStatus(int code, String reason) {
        this.code = code;
        this.reason = reason;
    }

    public int code() { return code; }
    public String reason() { return reason; }

    public boolean isSuccess() { return code >= 200 && code < 300; }
    public boolean isError() { return code >= 400; }
}

// Usage
HttpStatus status = HttpStatus.OK;
System.out.println(status.code());     // 200
System.out.println(status.isSuccess()); // true

Java 14+ introduced switch expressions with exhaustiveness checking for enums. The compiler verifies that every enum value is handled, eliminating an entire class of bugs where a new enum variant is added but not handled in existing switch statements.

Immutable Collections

Java 9 introduced factory methods for creating immutable collections: List.of(), Set.of(), and Map.of(). These are the preferred way to define constant collections because they are truly unmodifiable — any attempt to modify them throws UnsupportedOperationException.

java
// Java 9+ immutable collections
public static final List<String> SUPPORTED_FORMATS = List.of("png", "jpg", "webp", "gif");
public static final Set<String> RESERVED_WORDS = Set.of("class", "interface", "enum", "record");
public static final Map<String, Integer> STATUS_CODES = Map.of(
    "OK", 200,
    "NOT_FOUND", 404,
    "ERROR", 500
);

// For pre-Java 9, use Collections.unmodifiable*
public static final List<String> LEGACY_LIST =
    Collections.unmodifiableList(Arrays.asList("a", "b", "c"));

Built-in Constants

Java provides constants through wrapper classes, the Math class, and various standard library classes:

java
// Math constants
Math.PI     // 3.141592653589793
Math.E      // 2.718281828459045

// Integer limits
Integer.MAX_VALUE  // 2_147_483_647
Integer.MIN_VALUE  // -2_147_483_648
Long.MAX_VALUE     // 9_223_372_036_854_775_807L

// Float/Double limits
Double.MAX_VALUE        // ~1.8e308
Double.MIN_VALUE        // ~4.9e-324 (smallest positive)
Double.POSITIVE_INFINITY
Double.NEGATIVE_INFINITY
Double.NaN

// Boolean
Boolean.TRUE
Boolean.FALSE

// Collections
Collections.EMPTY_LIST
Collections.EMPTY_MAP
Collections.EMPTY_SET

Common Patterns

  • Use static final for individual constants and enums for related groups of constants.
  • Define constants in an interface or a final utility class with a private constructor to prevent instantiation.
  • Prefer List.of(), Set.of(), and Map.of() over mutable collections for constant data.
  • Use Java records (Java 16+) for constant value objects: record Point(int x, int y) {} creates an immutable data class.
  • Be aware of compile-time constant inlining — changing a static final primitive or String requires recompiling all dependent code.