Using Constants Correctly

25 min read

Clean Code and Readability — review Java constants for naming, visibility, ownership, and mutability beyond simple static final syntax.

1. Introduction

Constants are values that have a fixed meaning within a particular part of an application.

In Java, constants are commonly declared using static final.

JAVA
private static final int MAX_RETRY_ATTEMPTS = 3;

Constants are useful because they give names to values that otherwise would appear as unexplained literals throughout the code.

However, simply replacing a literal with static final does not automatically produce good code.

Constants can also be misused.

Common problems include:

  • Poor constant names
  • Constants placed in the wrong class
  • One global Constants class containing unrelated values
  • Public constants exposing implementation details
  • Mutable objects declared as static final
  • Business values hardcoded as constants even though they should be configurable
  • Numeric status codes represented as constants instead of enums
  • Constants duplicated across modules
  • Constants with unclear units
  • Constants that belong to another domain
  • Compile-time constants unintentionally inlined into external Java clients

Consider:

JAVA
public static final int VALUE = 30;

This tells the developer almost nothing.

Compare it with:

JAVA
private static final Duration PAYMENT_TIMEOUT = Duration.ofSeconds(30);

The second declaration communicates:

  • What the value controls
  • What unit it uses
  • Where it belongs

Using constants correctly therefore involves more than syntax.

A good constant should have:

  • Clear meaning
  • Correct scope
  • Correct ownership
  • Appropriate visibility
  • Appropriate type
  • Safe mutability characteristics
  • A reason to exist as code rather than configuration

During Pull Request review, developers should evaluate not only whether a literal was extracted, but whether the resulting constant improves the design.

2. What This Topic Means

Using constants correctly means representing stable values in Java using meaningful names and placing them where their ownership is clear.

A constant should help developers understand the code.

For example:

JAVA
if (attempts >= 3) {
    rejectRequest();
}

can become:

JAVA
private static final int MAX_RETRY_ATTEMPTS = 3;

and:

JAVA
if (attempts >= MAX_RETRY_ATTEMPTS) {
    rejectRequest();
}

However, correct constant usage also requires answering several questions.

Does the value need a constant?

This is usually unnecessary:

JAVA
private static final int ONE = 1;

Then:

JAVA
count += ONE;

The constant makes the code worse rather than better.

Does the value belong in code?

This may be questionable:

JAVA
private static final int MAX_CONNECTION_POOL_SIZE = 50;

If operations teams need to tune the connection pool by environment, configuration is more appropriate.

Does the value represent a state?

This is weak:

JAVA
private static final int STATUS_ACTIVE = 1;
private static final int STATUS_DISABLED = 2;

An enum is usually clearer:

JAVA
public enum AccountStatus {
    ACTIVE,
    DISABLED
}

Is the object actually immutable?

This declaration:

JAVA
private static final List<String> SUPPORTED_COUNTRIES = new ArrayList<>();

does not make the list immutable.

final only prevents assigning another list reference.

The contents can still change.

Is the visibility appropriate?

A constant used by one class usually should not be:

JAVA
public static final

Prefer the narrowest useful visibility.

Using constants correctly is therefore a design and code-review concern, not merely a syntax rule.

3. Why It Matters in Real Projects

Readability

Good constant names explain business and technical rules.

Compare:

JAVA
if (requestCount > 100) {
    reject();
}

with:

JAVA
if (requestCount > MAX_REQUESTS_PER_MINUTE) {
    reject();
}

The second version communicates intent immediately.

Maintainability

If the same rule appears in several places, a properly owned constant can prevent inconsistent updates.

For example:

JAVA
MAXIMUM_REFUND_WINDOW_DAYS

can provide one definition for a refund policy within its owning component.

Reliability

Poorly managed constants can create subtle bugs.

For example:

JAVA
public static final List<String> ALLOWED_ROLES = new ArrayList<>();

may be modified accidentally at runtime.

A developer might assume it is immutable because it is final.

Debugging

Meaningful constants help developers interpret production conditions.

Seeing:

JAVA
REQUEST_TIMEOUT

is more useful than seeing:

JAVA
30000

Team Development

Well-scoped constants communicate ownership.

A developer can understand whether a value belongs to:

  • Payment processing
  • Account security
  • Shipping
  • Fraud detection
  • Notification infrastructure

without searching the entire project.

Scalability of the Codebase

Large projects can accumulate hundreds of unrelated constants.

If they all live inside:

JAVA
AppConstants

developers eventually face a dumping ground containing values from every domain.

Correct placement becomes increasingly important as the application grows.

Performance

Constants usually do not create significant runtime performance differences.

However, the values represented by constants—such as batch sizes, cache TTLs, timeouts, and connection limits—can strongly affect performance.

The code-review concern is whether such values are correctly modeled and configurable where necessary.

4. Core Concept

The core concept is:

A constant should give a stable value a meaningful name while keeping ownership, type, visibility, and mutability correct.

A typical Java constant looks like:

JAVA
private static final int MAX_RETRY_ATTEMPTS = 3;

static

static means the value belongs to the class rather than a particular object instance.

If every PaymentService instance uses the same retry limit, creating one copy per object is unnecessary.

final

final means the variable cannot be reassigned after initialization.

This is valid:

JAVA
private static final int MAX_RETRY_ATTEMPTS = 3;

This is not:

JAVA
MAX_RETRY_ATTEMPTS = 5;

Important Java Detail: final Does Not Mean Deeply Immutable

Consider:

JAVA
private static final List<String> SUPPORTED_COUNTRIES = new ArrayList<>();

You cannot do:

JAVA
SUPPORTED_COUNTRIES = new ArrayList<>();

but you can still do:

JAVA
SUPPORTED_COUNTRIES.add("IN");

Therefore:

JAVA
static final

protects the reference, not necessarily the object.

For immutable collections, use an immutable representation where appropriate:

JAVA
private static final Set<String> SUPPORTED_COUNTRIES = Set.of("IN", "SG", "AE");

Compile-Time Constants

Certain static final primitive and String values are compile-time constants.

Example:

JAVA
public static final int MAX_SIZE = 100;

When another Java class compiles against this constant, the value may be inlined into the caller's bytecode.

If a library later changes:

JAVA
MAX_SIZE = 200;

an already compiled client may still use 100 until it is recompiled.

This is one reason to be careful when exposing primitive or String constants as public APIs across separately deployed modules.

Constants Should Reflect Ownership

A payment retry count belongs closer to payment integration code than to:

JAVA
GlobalConstants

The correct location communicates which part of the system owns the decision.

5. Important Rules

  • Use meaningful names that explain purpose, not value.
  • Follow Java naming conventions such as uppercase words separated by underscores.
  • Prefer private visibility unless broader access is genuinely required.
  • Place constants near the code or domain that owns them.
  • Do not create a global constants dumping ground.
  • Use static final for stable class-level values.
  • Remember that final references can still point to mutable objects.
  • Use immutable collections for shared constant collections.
  • Use enums when representing a finite set of domain states.
  • Use configuration properties for values that vary by environment or operational requirements.
  • Use Duration instead of raw numbers for time periods where practical.
  • Use BigDecimal for precise monetary rates and thresholds.
  • Include units in names when the Java type does not already express them.
  • Avoid unnecessary public constants.
  • Be cautious with public compile-time constants shared across separately compiled modules.
  • Do not extract naturally obvious numbers into meaningless constants.
  • Avoid duplicate definitions of the same business policy.
  • Keep constants cohesive with the class that owns them.
  • Validate externally configured values instead of assuming they are correct.
  • Do not use constants to hide poor domain modeling.

6. Bad Code Example

Consider a Spring Boot file-upload service.

JAVA
@Service
public class DocumentUploadService {
    public static final int SIZE = 10485760;
    public static final int TIMEOUT = 30000;
    public static final String TYPE1 = "PDF";
    public static final String TYPE2 = "DOCX";
    public static final List<String> TYPES = new ArrayList<>();

    static {
        TYPES.add(TYPE1);
        TYPES.add(TYPE2);
    }

    private final VirusScannerClient virusScannerClient;
    private final DocumentRepository documentRepository;

    public DocumentUploadService(VirusScannerClient virusScannerClient, DocumentRepository documentRepository) {
        this.virusScannerClient = virusScannerClient;
        this.documentRepository = documentRepository;
    }

    public Document upload(DocumentUploadRequest request) {
        if (request.getContent().length > SIZE) {
            throw new IllegalArgumentException("File is too large");
        }

        if (!TYPES.contains(request.getFileType())) {
            throw new IllegalArgumentException("Unsupported file type");
        }

        virusScannerClient.scan(request.getContent(), TIMEOUT);

        Document document = new Document();
        document.setName(request.getFileName());
        document.setType(request.getFileType());
        document.setStatus("UPLOADED");

        return documentRepository.save(document);
    }
}

The class uses constants, but several of them are poorly designed.

7. Problems in the Bad Code

Poor Constant Names

These names provide very little meaning:

JAVA
SIZE
TIMEOUT
TYPE1
TYPE2
TYPES

A reviewer must inspect their usage to understand them.

Missing Units

This constant:

JAVA
TIMEOUT = 30000

does not indicate whether the value represents:

  • Milliseconds
  • Seconds
  • Microseconds

The implementation happens to pass it to a client method, but the meaning is still unclear.

Public Visibility Without Need

The constants are declared:

JAVA
public static final

even though they appear to be implementation details of the upload service.

This unnecessarily expands the public API.

Mutable static final Collection

This is particularly dangerous:

JAVA
public static final List<String> TYPES = new ArrayList<>();

Although the reference is final, callers can mutate the collection:

JAVA
DocumentUploadService.TYPES.clear();

Now every upload may fail validation.

Raw String Domain Values

Values such as:

JAVA
"PDF"
"DOCX"
"UPLOADED"

represent states or categories.

Enums may provide safer modeling.

Mixed Concerns

File-size limits, scan timeouts, supported file types, and document status represent different concerns.

They should not automatically be treated as one group simply because they are constants.

Potential Configuration Requirement

Maximum upload size and scanner timeout may vary by deployment environment.

If so, hardcoded constants are inappropriate.

Numeric Size Is Hard to Interpret

This:

JAVA
10485760

requires the developer to calculate that it represents approximately 10 MiB.

Weak Type Safety

Using raw strings allows values such as:

JAVA
"pdf"
"Pdf"
"WORD"

unless validation catches them manually.

8. Code Review Findings

A senior Java reviewer should notice:

  • Constants exist, but their names do not communicate purpose.
  • SIZE should clearly express the maximum document size.
  • TIMEOUT should express both purpose and unit, or use Duration.
  • The TYPES list is mutable despite being static final.
  • Public visibility appears unnecessary.
  • File types would be better represented using an enum.
  • Document status should also be strongly typed if the domain supports it.
  • Upload size and scanner timeout may belong in configuration.
  • The class should not expose mutable internal state through public constants.
  • The values should be grouped according to ownership rather than placed together merely because they are constants.

The reviewer should also ask whether the upload-size limit is an application policy or infrastructure setting.

If an API gateway already enforces another size limit, these values should be aligned.

9. Reviewer Comment Example

SIZE does not explain what the value limits. Could we rename this to something like MAX_DOCUMENT_SIZE_BYTES, or use configuration if the limit varies by environment?

TIMEOUT is ambiguous because the unit is not visible. A Duration would make the scanner timeout clearer.

TYPES is a mutable ArrayList. static final prevents reassignment but does not prevent callers from modifying its contents. Please use an immutable collection or an enum.

These constants appear to be internal implementation details. Can we reduce their visibility from public?

PDF and DOCX look like domain values rather than arbitrary strings. An enum would remove string-case and typo issues.

Please avoid exposing mutable collections as public constants. One caller could modify validation behavior for the entire application.

10. Improved Code

JAVA
@Service
public class DocumentUploadService {
    private static final long MAX_DOCUMENT_SIZE_BYTES = 10L * 1024 * 1024;
    private static final Duration VIRUS_SCAN_TIMEOUT = Duration.ofSeconds(30);
    private static final Set<DocumentType> SUPPORTED_DOCUMENT_TYPES = Set.of(
            DocumentType.PDF,
            DocumentType.DOCX
    );

    private final VirusScannerClient virusScannerClient;
    private final DocumentRepository documentRepository;

    public DocumentUploadService(VirusScannerClient virusScannerClient, DocumentRepository documentRepository) {
        this.virusScannerClient = virusScannerClient;
        this.documentRepository = documentRepository;
    }

    public Document upload(DocumentUploadRequest request) {
        validateDocument(request);

        virusScannerClient.scan(request.getContent(), VIRUS_SCAN_TIMEOUT);

        Document document = new Document();
        document.setName(request.getFileName());
        document.setType(request.getFileType());
        document.setStatus(DocumentStatus.UPLOADED);

        return documentRepository.save(document);
    }

    private void validateDocument(DocumentUploadRequest request) {
        if (request.getContent().length > MAX_DOCUMENT_SIZE_BYTES) {
            throw new IllegalArgumentException("File exceeds maximum allowed size");
        }

        if (!SUPPORTED_DOCUMENT_TYPES.contains(request.getFileType())) {
            throw new IllegalArgumentException("Unsupported document type");
        }
    }
}

Enums:

JAVA
public enum DocumentType {
    PDF,
    DOCX
}
JAVA
public enum DocumentStatus {
    UPLOADED,
    SCANNED,
    REJECTED
}

If the limits must be configurable:

JAVA
@ConfigurationProperties(prefix = "document.upload")
public record DocumentUploadProperties(
        DataSize maximumSize,
        Duration virusScanTimeout) {
}

Configuration:

PROPERTIES
document.upload.maximum-size=10MB
document.upload.virus-scan-timeout=30s

11. Improved Code Explanation

Constant Names Explain Purpose

Instead of:

JAVA
SIZE

the code uses:

JAVA
MAX_DOCUMENT_SIZE_BYTES

The meaning and unit are clear.

Time Uses a Domain-Appropriate Type

Instead of:

JAVA
30000

the code uses:

JAVA
Duration.ofSeconds(30)

This reduces unit-conversion mistakes.

Collection Is Immutable

This:

JAVA
Set.of(...)

creates an immutable set.

Callers cannot accidentally change supported document types.

Visibility Is Restricted

Constants are private because they are implementation details.

Enums Replace Raw Strings

DocumentType and DocumentStatus provide compile-time safety.

Configuration Can Be Used Where Appropriate

If maximum upload size or scanner timeout changes by environment, Spring Boot configuration properties provide better operational control.

Responsibilities Are Clearer

A document type is represented as domain data.

A timeout is represented as infrastructure configuration.

They are no longer treated as equivalent simply because both are fixed values.

12. Bad Code vs Improved Code

AspectBad CodeImproved Code
NamingSIZE, TIMEOUT, TYPE1Purpose-specific names
VisibilityPublic constantsNarrow private scope
Time handlingRaw integerDuration
Collection safetyMutable ArrayListImmutable Set
Domain modelingRaw stringsEnums
MaintainabilityMeaning inferred from usageMeaning visible in declaration
ConfigurationHardcoded operational valuesCan use validated configuration
ReliabilityShared collection can be mutatedConstant data cannot be modified

13. Real Project Scenario

Consider an employee payroll platform.

A shared class is created early in the project:

JAVA
public class AppConstants {
    public static final BigDecimal TAX = new BigDecimal("0.20");
    public static final int DAYS = 30;
    public static final int LIMIT = 100;
    public static final String ACTIVE = "A";
    public static final String INACTIVE = "I";
    public static final int TIMEOUT = 5000;
}

Over several years, dozens of services begin importing these values.

Eventually developers no longer know:

  • Which tax the TAX constant represents
  • What DAYS controls
  • Which feature uses LIMIT
  • Whether TIMEOUT means milliseconds
  • Whether "A" is employee status, payroll status, or account status

A payroll policy changes.

One developer updates:

JAVA
AppConstants.TAX

without realizing that another module reused the same constant for a different calculation.

The result is an unintended production change.

A better design gives values domain ownership.

For example:

JAVA
public final class PayrollPolicy {
    private PayrollPolicy() {
    }

    public static final BigDecimal STANDARD_TAX_RATE = new BigDecimal("0.20");
}

Employee state should be modeled separately:

JAVA
public enum EmployeeStatus {
    ACTIVE,
    INACTIVE
}

Infrastructure timeout belongs with the integration configuration rather than payroll policy.

Correct constant placement prevents unrelated modules from becoming coupled through generic values.

14. Production Impact

Incorrect constant usage can create real production problems.

Shared Mutable State

A public mutable constant collection can be modified by any caller.

Example:

JAVA
public static final Set<String> BLOCKED_COUNTRIES = new HashSet<>();

A single accidental:

JAVA
BLOCKED_COUNTRIES.clear();

may change application-wide validation.

Incorrect Business Behavior

Generic constants may be reused for unrelated business rules simply because the value happens to match.

Configuration Rigidity

Operational values hardcoded as constants require code deployment for every change.

Incorrect Time Units

A timeout intended as 30 seconds might accidentally be interpreted as 30 milliseconds or 30 minutes.

Binary Compatibility Surprise

A public compile-time constant in a shared Java library can be inlined into consuming code.

Suppose library version 1 contains:

JAVA
public static final int MAX_BATCH_SIZE = 100;

A consuming service is compiled.

Later the library changes:

JAVA
public static final int MAX_BATCH_SIZE = 200;

If the consuming service is not recompiled, its previously compiled bytecode may continue using 100.

This matters particularly for shared libraries distributed independently.

Accidental API Coupling

Public constants become part of the API surface and can be imported across modules.

Changing or removing them later becomes harder.

15. Common Developer Mistakes

Creating a Global Constants Class

Example:

JAVA
CommonConstants
ApplicationConstants
GlobalConstants

These classes often become dumping grounds.

Using Meaningless Names

Bad:

JAVA
VALUE
LIMIT
SIZE
TIMEOUT
DEFAULT

without business context.

Making Every Constant Public

Most constants do not need application-wide visibility.

Assuming final Means Immutable

Bad:

JAVA
public static final List<String> VALUES = new ArrayList<>();

The list contents remain mutable.

Using Constants Instead of Enums

Bad:

JAVA
public static final int ACTIVE = 1;
public static final int DISABLED = 2;

Converting Every Literal Into a Constant

Bad:

JAVA
private static final int ZERO = 0;
private static final int ONE = 1;

when they add no business meaning.

Using Constants for Frequently Changing Settings

A production timeout may belong in configuration.

Duplicating Constants Across Modules

Several teams independently declare:

JAVA
MAXIMUM_REFUND_DAYS = 30;

even though it represents the same policy.

Exposing Mutable Arrays

Bad:

JAVA
public static final String[] SUPPORTED_TYPES = {"A", "B"};

Callers can modify array elements.

Ignoring Units

Bad:

JAVA
private static final long RETRY_DELAY = 5000;

Using Interface Constants

Example:

JAVA
public interface PaymentConstants {
    int MAX_ATTEMPTS = 3;
}

Classes should not implement an interface merely to inherit constants.

Interfaces should primarily describe behavior contracts.

16. Edge Cases

static final Mutable Objects

This is one of the most important Java-specific edge cases.

JAVA
private static final Map<String, Integer> LIMITS = new HashMap<>();

The map can still be changed.

Prefer immutable collections where data must remain constant.

Arrays

Even with:

JAVA
private static final String[] SUPPORTED_TYPES = {"PDF", "DOCX"};

the array elements remain mutable.

Use immutable collections or defensive copies.

Constants That Depend on Runtime Values

This:

JAVA
private static final Instant START_TIME = Instant.now();

is final, but it is not a compile-time constant.

It captures one runtime value when the class initializes.

Reviewers should distinguish fixed references from compile-time constants.

Environment-Specific Values

Database pool size may be 20 in development and 100 in production.

A Java constant is probably inappropriate.

Locale or Region-Specific Rules

A minimum age or transaction limit may differ by country.

One universal constant may incorrectly model the business.

Currency-Specific Values

A payment threshold cannot necessarily be represented by:

JAVA
MAX_PAYMENT_AMOUNT

without considering currency.

Public API Constants

Changing public static final primitives or String constants in shared libraries can require consumer recompilation because of constant inlining.

Test Constants

Tests may use constants for fixtures, but they should not hide test intent.

A test containing:

JAVA
TEST_VALUE_1

may be less understandable than the literal if the actual value matters to the scenario.

17. Performance Considerations

Using named constants has negligible runtime overhead.

There is generally no meaningful performance difference between:

JAVA
if (attempts >= 3)

and:

JAVA
if (attempts >= MAX_RETRY_ATTEMPTS)

The JVM and compiler handle such values efficiently.

However, the values represented by constants can affect performance significantly.

Batch Size

JAVA
MAX_BATCH_SIZE

can influence:

  • Database round trips
  • Heap usage
  • Transaction size
  • Throughput

Connection Limits

A connection-pool size determines resource concurrency.

Timeout Values

Timeouts affect:

  • Thread occupancy
  • Latency
  • Downstream pressure

Cache TTL

A cache lifetime influences:

  • Memory
  • Database load
  • Data freshness

Retry Counts

Excessive retry constants can multiply downstream traffic during outages.

The reviewer should therefore ask whether performance-sensitive constants should be configurable and validated.

The performance issue is normally the policy value itself, not the use of a constant.

18. Security Considerations

Constants may represent security-sensitive policies.

Examples include:

JAVA
MAX_FAILED_LOGIN_ATTEMPTS
ACCESS_TOKEN_TTL
PASSWORD_RESET_TOKEN_TTL
MINIMUM_PASSWORD_LENGTH
MAX_REQUESTS_PER_MINUTE

These values should have clear ownership and consistent usage.

Do Not Store Secrets as Constants

Bad:

JAVA
private static final String API_KEY = "production-secret";

Secrets should come from secure configuration or secret-management systems.

Avoid Public Security Policy Constants

Exposing internal security implementation values unnecessarily can create coupling.

Consistent Authorization Values

Roles and permissions should normally not be modeled using unexplained numeric constants.

Bad:

JAVA
ROLE_ADMIN = 1
ROLE_USER = 2

Prefer strongly typed security models.

Mutable Security Collections

This is dangerous:

JAVA
public static final Set<String> ADMIN_ROLES = new HashSet<>();

If mutable, application security behavior can change at runtime.

Configurable Security Values

Some limits may need configuration, but their values should be validated.

For example, a configuration value of:

TEXT
max-failed-login-attempts=0

could accidentally lock every account immediately.

19. Testing Considerations

Constants that control business boundaries should be tested at their boundaries.

Suppose:

JAVA
private static final int MAX_EXPORT_RECORDS = 10000;

Test:

  • 9999
  • 10000
  • 10001

Test Meaning, Not Constant Implementation

Avoid writing tests whose only purpose is:

JAVA
assertEquals(10000, MAX_EXPORT_RECORDS);

unless the constant value itself is part of a public contract.

Instead, test behavior.

Example:

JAVA
@Test
void shouldAllowExportAtMaximumSupportedRecordCount() {
    ExportRequest request = requestWithRecordCount(10000);

    assertDoesNotThrow(() -> exportService.validate(request));
}

Immutable Collection Test

If a public immutable collection is intentionally exposed, verify that modification is not possible.

Usually, however, exposing such collections publicly should itself be questioned.

Configuration Tests

If a constant is converted to Spring configuration, verify:

  • Property binding
  • Validation
  • Default values
  • Missing values
  • Invalid values

Time-Based Constants

Use Duration and injected Clock where time behavior needs deterministic tests.

Cross-Module Compatibility

For shared libraries, test public contracts carefully before changing constants that external clients may compile against.

20. Refactoring Guidelines

Step 1: Identify the Value

Find a literal or poorly named constant.

Example:

JAVA
private static final int LIMIT = 100;

Step 2: Determine Meaning

Ask what 100 actually represents.

Perhaps:

JAVA
MAXIMUM_EXPORT_RECORDS

Step 3: Determine Ownership

Should the value live in:

  • Current service
  • Domain policy
  • Configuration class
  • Enum
  • Shared library

Step 4: Determine Correct Type

Instead of:

JAVA
long TIMEOUT = 30000;

consider:

JAVA
Duration REQUEST_TIMEOUT = Duration.ofSeconds(30);

Step 5: Restrict Visibility

Start with:

JAVA
private

and widen only when required.

Step 6: Check Mutability

If the constant refers to a collection or object, ensure callers cannot modify shared state unexpectedly.

Step 7: Replace Raw Domain Codes

Move numeric or string state constants to enums where appropriate.

Step 8: Check Whether Configuration Is Better

If the value must change independently of deployment, externalize it.

Step 9: Search for Duplicate Definitions

Confirm that the same policy is not independently defined elsewhere.

Step 10: Add or Update Boundary Tests

Verify behavior without changing the business rule during refactoring.

21. Best Practices

  • Give constants intention-revealing names.
  • Keep constants close to their domain owner.
  • Use the narrowest practical visibility.
  • Use private static final for class-specific stable values.
  • Prefer immutable objects for shared constants.
  • Prefer Set.of, List.of, or Map.of for fixed collections.
  • Use enums for finite domain states.
  • Use Duration for time-based values.
  • Use BigDecimal for precise financial values.
  • Include units in names when necessary.
  • Use Spring configuration properties for operational values.
  • Validate configuration properties.
  • Avoid generic constants classes.
  • Avoid public constants unless they are truly part of a supported API.
  • Consider compile-time inlining before exposing primitive or String constants from shared libraries.
  • Keep business rules and infrastructure settings separately owned.
  • Write tests around constant-driven behavior.
  • Revisit constants when requirements become dynamic.

22. Practices to Avoid

Generic Global Constants Classes

Avoid:

JAVA
public class AppConstants {
    ...
}

when it collects unrelated values from every domain.

Interface Constants

Avoid:

JAVA
public interface Constants {
    int MAX_RETRY = 3;
}

especially when classes implement the interface only to access constants.

Mutable Public Constants

Avoid:

JAVA
public static final List<String> VALUES = new ArrayList<>();

Meaningless Names

Avoid:

JAVA
VALUE_1
NUMBER_30
LIMIT
DATA

Public by Default

Do not use:

JAVA
public static final

unless other components genuinely require access.

Hardcoded Secrets

Never use constants for production credentials.

Raw Numeric States

Avoid:

JAVA
STATUS_ACTIVE = 1
STATUS_DISABLED = 2

when an enum communicates the domain better.

Raw Time Units

Avoid:

JAVA
TIMEOUT = 30000

without explicit meaning.

Constant Explosion

Do not create a constant for every small literal.

Using Constants to Hide Bad Design

Changing:

JAVA
if (type == 1)

to:

JAVA
if (type == TYPE_ONE)

may still be inferior to proper domain modeling.

23. Code Review Checklist

  • Does this constant have a meaningful name?
  • Does the name explain purpose rather than value?
  • Is the constant located in the correct domain or component?
  • Is its visibility wider than necessary?
  • Does this value really need to be public?
  • Should this value be configuration instead of code?
  • Does the value vary by environment?
  • Is the type appropriate?
  • Would Duration communicate time more clearly?
  • Is BigDecimal appropriate for this monetary value?
  • Does the constant reference a mutable object?
  • Can callers modify a shared collection or array?
  • Would an immutable collection be safer?
  • Does this value represent a domain state that should be an enum?
  • Is the same constant duplicated elsewhere?
  • Are multiple different concepts sharing one generic constant?
  • Are units clear?
  • Is a secret being stored as a constant?
  • Could this public compile-time constant be inlined into external callers?
  • Would changing it require downstream recompilation?
  • Are boundary conditions covered by tests?
  • Is the constant actually improving readability?
  • Is a global constants class becoming a dumping ground?
  • Does the resulting code have clearer ownership?

24. Common Pull Request Review Comments

  1. *Can we rename LIMIT to describe what is actually being limited? The current name does not communicate the business rule.*
  1. *This constant is only used inside this service. Please reduce its visibility from public to private unless there is a real external contract.*
  1. *static final does not make this ArrayList immutable. Can we use List.of or another immutable representation?*
  1. *This timeout is expressed as a raw integer. A Duration would make the unit and intent much clearer.*
  1. *These status constants model a fixed set of domain states. I think an enum would provide stronger type safety.*
  1. *Does this threshold need to vary between environments? If so, configuration may be a better owner than a Java constant.*
  1. *I would avoid adding another value to CommonConstants. This one belongs specifically to the refund domain.*
  1. *This public primitive constant is part of a shared library. Please consider Java constant inlining and whether downstream services are guaranteed to recompile when it changes.*
  1. *Please avoid exposing this mutable array as a public constant. A caller can modify its elements globally.*
  1. *The value is extracted, but the name VALUE_30 still does not explain its meaning. Can we name it after the actual policy?*

25. Code Review Exercise

Review the following Spring Boot report-export service.

Identify:

  • Problems
  • Code smells
  • Risks
  • Improvements

Do not read the solution until completing your review.

JAVA
@Service
public class ReportExportService {
    public static final int LIMIT = 10000;
    public static final long WAIT_TIME = 60000;
    public static final String CSV = "1";
    public static final String PDF = "2";
    public static final List<String> FORMATS = new ArrayList<>();

    static {
        FORMATS.add(CSV);
        FORMATS.add(PDF);
    }

    private final ReportRepository reportRepository;

    public ReportExportService(ReportRepository reportRepository) {
        this.reportRepository = reportRepository;
    }

    public ExportResult export(ExportRequest request) {
        if (!FORMATS.contains(request.getFormat())) {
            throw new IllegalArgumentException("Unsupported format");
        }

        List<ReportRow> rows = reportRepository.findRows(request.getReportId());

        if (rows.size() > LIMIT) {
            throw new IllegalStateException("Too many records");
        }

        if (request.getWaitTime() > WAIT_TIME) {
            throw new IllegalArgumentException("Wait time is too large");
        }

        return new ExportResult(rows, request.getFormat());
    }
}

Review:

  • Naming
  • Visibility
  • Mutability
  • Raw domain values
  • Units
  • Configuration needs
  • Constant ownership
  • Boundary behavior

26. Exercise Solution

Problems Identified

1. LIMIT Is Too Generic

This:

JAVA
LIMIT

does not explain what is limited.

A better name is:

JAVA
MAXIMUM_EXPORT_RECORDS

2. WAIT_TIME Has No Unit

This:

JAVA
60000

appears to mean milliseconds.

The unit should be represented explicitly.

3. Numeric Strings Represent Domain States

These constants:

JAVA
CSV = "1"
PDF = "2"

represent export formats.

An enum is more appropriate.

4. Mutable Public Collection

This:

JAVA
public static final List<String> FORMATS = new ArrayList<>();

can be modified by any caller.

5. Unnecessary Public API

There is no demonstrated reason for other classes to access these implementation values directly.

6. Possible Configuration Requirement

Maximum export size may be an operational policy.

If different environments need different limits, Spring configuration is preferable.

7. Wait-Time Ownership Is Unclear

The service accepts request.getWaitTime() and compares it against a constant.

The reviewer should understand what the wait time actually controls before deciding its final owner.

Improved Code

JAVA
@Service
public class ReportExportService {
    private static final int MAXIMUM_EXPORT_RECORDS = 10000;
    private static final Duration MAXIMUM_EXPORT_WAIT = Duration.ofMinutes(1);

    private final ReportRepository reportRepository;

    public ReportExportService(ReportRepository reportRepository) {
        this.reportRepository = reportRepository;
    }

    public ExportResult export(ExportRequest request) {
        validateRequest(request);

        List<ReportRow> rows = reportRepository.findRows(request.getReportId());

        if (rows.size() > MAXIMUM_EXPORT_RECORDS) {
            throw new IllegalStateException("Export exceeds maximum supported record count");
        }

        return new ExportResult(rows, request.getFormat());
    }

    private void validateRequest(ExportRequest request) {
        if (request.getFormat() == null) {
            throw new IllegalArgumentException("Export format is required");
        }

        if (request.getWaitTime().compareTo(MAXIMUM_EXPORT_WAIT) > 0) {
            throw new IllegalArgumentException("Export wait time exceeds maximum");
        }
    }
}

Enum:

JAVA
public enum ExportFormat {
    CSV,
    PDF
}

Example request:

JAVA
public class ExportRequest {
    private Long reportId;
    private ExportFormat format;
    private Duration waitTime;

    public Long getReportId() {
        return reportId;
    }

    public ExportFormat getFormat() {
        return format;
    }

    public Duration getWaitTime() {
        return waitTime;
    }
}

Configuration Alternative

If the export limit is operationally adjustable:

JAVA
@ConfigurationProperties(prefix = "report.export")
public record ReportExportProperties(
        int maximumRecords,
        Duration maximumWait) {
}

Configuration:

PROPERTIES
report.export.maximum-records=10000
report.export.maximum-wait=1m

Why Each Change Is Useful

  • Constant names communicate business purpose.
  • Duration eliminates time-unit ambiguity.
  • ExportFormat removes numeric-string domain codes.
  • Mutable shared collections disappear.
  • Visibility is restricted.
  • Configuration can own values that operations need to tune.
  • The service API becomes more strongly typed.

27. Interview Perspective

Using constants correctly is a common code-review topic because it tests more than knowledge of static final.

Java Interview

A candidate may be asked:

What is the difference between final and immutability?

A strong answer should explain that:

JAVA
final

prevents reassignment of the reference.

It does not make the referenced object immutable.

For example:

JAVA
final List<String> values = new ArrayList<>();

still allows:

JAVA
values.add("A");

Java Library Interview

An advanced question may involve:

JAVA
public static final int VERSION = 1;

The candidate should know that compile-time constants can be inlined into consuming classes.

Changing the value in a library does not necessarily change already compiled clients until they are recompiled.

Spring Boot Interview

A candidate may be asked:

Should this timeout be a constant or configuration property?

A good answer considers:

  • Environment variation
  • Operational tuning
  • Deployment independence
  • Ownership

Senior Developer Interview

The interviewer may show:

JAVA
CommonConstants

with 200 unrelated fields.

A senior candidate should recognize poor cohesion and weak domain ownership.

Code Review Interview

Candidates should distinguish:

JAVA
static final

from correct constant design.

The reviewer should examine:

  • Naming
  • Scope
  • Type
  • Mutability
  • Ownership
  • Configuration needs

28. Interview Questions and Answers

Basic Question

Question: How do you normally declare a constant in Java?

Answer:

A class-level constant is commonly declared using static final, for example:

JAVA
private static final int MAX_RETRY_ATTEMPTS = 3;

The name should describe the value's purpose, and visibility should be as narrow as practical.

Intermediate Question

Question: Does static final List<String> create an immutable list?

Answer:

No. final prevents the field from referencing a different list, but the existing list can still be modified. For fixed values, use an immutable collection such as List.of(...), Set.of(...), or another immutable representation.

Advanced Question

Question: What problem can occur when changing a public static final int in a shared Java library?

Answer:

If the field is a compile-time constant, Java clients may inline its value when they compile. Changing the library constant later does not necessarily update already compiled consumers. Those consumers may continue using the old value until they are recompiled.

Scenario-Based Question

Question: A timeout is currently declared as private static final long TIMEOUT = 30000. How would you review it?

Answer:

I would first ask what the timeout controls and whether it varies by environment. At minimum, the unit should be explicit. A Duration, such as Duration.ofSeconds(30), is clearer. If operations need to tune it, I would move it to validated Spring configuration.

Code-Review Question

Question: What is wrong with a Constants class containing payment limits, JWT expiration, page sizes, and shipping thresholds?

Answer:

Those values belong to unrelated responsibilities. A single global constants class reduces cohesion and hides ownership. Constants should normally live near the domain or technical component that owns them.

Real-Project Question

Question: How do you decide between a constant, enum, and configuration property?

Answer:

I use a constant for a stable value owned by code, an enum for a fixed set of meaningful domain states, and configuration when the value should vary by environment or change independently of code deployment. The decision is based on meaning and ownership, not convenience.

29. Quick Rule to Remember

A good constant should make its meaning, ownership, and unit obvious without forcing the reader to inspect where it is used.

30. Final Takeaway

What the Developer Should Remember

Correct constant usage is not simply:

JAVA
static final

The developer should also consider:

  • What the value means
  • Who owns it
  • Where it should live
  • Whether it should be configurable
  • Whether it should be an enum
  • Whether the referenced object is actually immutable
  • Whether other modules should be allowed to access it

Prefer:

JAVA
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(30);

over:

JAVA
public static final int TIMEOUT = 30000;

Prefer:

JAVA
AccountStatus.ACTIVE

over:

JAVA
STATUS_ACTIVE = 1

Prefer immutable collections over:

JAVA
public static final List<String> VALUES = new ArrayList<>();

Developers should:

  • Use meaningful names.
  • Keep constants close to their owner.
  • Restrict visibility.
  • Use correct types.
  • Avoid mutable shared constants.
  • Use enums for states.
  • Use configuration for adjustable settings.
  • Avoid global constants dumping grounds.
  • Be careful with public compile-time constants.
  • Test behavior at important boundaries.

What the Reviewer Should Check

During Pull Request review, ask:

  • Does this constant improve readability?
  • Is its name meaningful?
  • Is its scope appropriate?
  • Does another class really need access to it?
  • Is the object immutable?
  • Can callers mutate it?
  • Should this be an enum?
  • Should this be configuration?
  • Are units clear?
  • Is the value duplicated elsewhere?
  • Does it belong to the correct domain?
  • Is a global constants class hiding weak ownership?
  • Could Java constant inlining affect external consumers?
  • Are important boundaries tested?

What Should Be Avoided in Production Code

Avoid constants such as:

JAVA
VALUE
DATA
LIMIT
NUMBER_30
TYPE1
TYPE2

Avoid global classes containing every value used by the application.

Avoid public mutable arrays, lists, sets, and maps presented as constants.

Avoid assuming final means immutable.

Avoid numeric status codes when enums provide clearer modeling.

Avoid hardcoding operational values when environment-specific configuration is required.

Avoid exposing compile-time constants across module boundaries without understanding Java inlining behavior.

The production-quality approach is:

Use constants to communicate stable meaning, keep them close to the code or domain that owns them, choose the correct type and visibility, and never use static final as a substitute for proper domain modeling or configuration.