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.
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
Constantsclass 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:
public static final int VALUE = 30;This tells the developer almost nothing.
Compare it with:
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:
if (attempts >= 3) {
rejectRequest();
}can become:
private static final int MAX_RETRY_ATTEMPTS = 3;and:
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:
private static final int ONE = 1;Then:
count += ONE;The constant makes the code worse rather than better.
Does the value belong in code?
This may be questionable:
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:
private static final int STATUS_ACTIVE = 1;
private static final int STATUS_DISABLED = 2;An enum is usually clearer:
public enum AccountStatus {
ACTIVE,
DISABLED
}Is the object actually immutable?
This declaration:
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:
public static finalPrefer 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:
if (requestCount > 100) {
reject();
}with:
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:
MAXIMUM_REFUND_WINDOW_DAYScan provide one definition for a refund policy within its owning component.
Reliability
Poorly managed constants can create subtle bugs.
For example:
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:
REQUEST_TIMEOUTis more useful than seeing:
30000Team 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:
AppConstantsdevelopers 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:
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:
private static final int MAX_RETRY_ATTEMPTS = 3;This is not:
MAX_RETRY_ATTEMPTS = 5;Important Java Detail: final Does Not Mean Deeply Immutable
Consider:
private static final List<String> SUPPORTED_COUNTRIES = new ArrayList<>();You cannot do:
SUPPORTED_COUNTRIES = new ArrayList<>();but you can still do:
SUPPORTED_COUNTRIES.add("IN");Therefore:
static finalprotects the reference, not necessarily the object.
For immutable collections, use an immutable representation where appropriate:
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:
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:
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:
GlobalConstantsThe 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
privatevisibility 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 finalfor stable class-level values. - Remember that
finalreferences 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
Durationinstead of raw numbers for time periods where practical. - Use
BigDecimalfor 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.
@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:
SIZE
TIMEOUT
TYPE1
TYPE2
TYPESA reviewer must inspect their usage to understand them.
Missing Units
This constant:
TIMEOUT = 30000does 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:
public static finaleven 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:
public static final List<String> TYPES = new ArrayList<>();Although the reference is final, callers can mutate the collection:
DocumentUploadService.TYPES.clear();Now every upload may fail validation.
Raw String Domain Values
Values such as:
"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:
10485760requires the developer to calculate that it represents approximately 10 MiB.
Weak Type Safety
Using raw strings allows values such as:
"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.
SIZEshould clearly express the maximum document size.TIMEOUTshould express both purpose and unit, or useDuration.- The
TYPESlist is mutable despite beingstatic 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
SIZEdoes not explain what the value limits. Could we rename this to something likeMAX_DOCUMENT_SIZE_BYTES, or use configuration if the limit varies by environment?
TIMEOUTis ambiguous because the unit is not visible. ADurationwould make the scanner timeout clearer.
TYPESis a mutableArrayList.static finalprevents 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?
DOCXlook 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
@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:
public enum DocumentType {
PDF,
DOCX
}public enum DocumentStatus {
UPLOADED,
SCANNED,
REJECTED
}If the limits must be configurable:
@ConfigurationProperties(prefix = "document.upload")
public record DocumentUploadProperties(
DataSize maximumSize,
Duration virusScanTimeout) {
}Configuration:
document.upload.maximum-size=10MB
document.upload.virus-scan-timeout=30s11. Improved Code Explanation
Constant Names Explain Purpose
Instead of:
SIZEthe code uses:
MAX_DOCUMENT_SIZE_BYTESThe meaning and unit are clear.
Time Uses a Domain-Appropriate Type
Instead of:
30000the code uses:
Duration.ofSeconds(30)This reduces unit-conversion mistakes.
Collection Is Immutable
This:
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
| Aspect | Bad Code | Improved Code |
|---|---|---|
| Naming | SIZE, TIMEOUT, TYPE1 | Purpose-specific names |
| Visibility | Public constants | Narrow private scope |
| Time handling | Raw integer | Duration |
| Collection safety | Mutable ArrayList | Immutable Set |
| Domain modeling | Raw strings | Enums |
| Maintainability | Meaning inferred from usage | Meaning visible in declaration |
| Configuration | Hardcoded operational values | Can use validated configuration |
| Reliability | Shared collection can be mutated | Constant data cannot be modified |
13. Real Project Scenario
Consider an employee payroll platform.
A shared class is created early in the project:
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
TAXconstant represents - What
DAYScontrols - Which feature uses
LIMIT - Whether
TIMEOUTmeans milliseconds - Whether
"A"is employee status, payroll status, or account status
A payroll policy changes.
One developer updates:
AppConstants.TAXwithout 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:
public final class PayrollPolicy {
private PayrollPolicy() {
}
public static final BigDecimal STANDARD_TAX_RATE = new BigDecimal("0.20");
}Employee state should be modeled separately:
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:
public static final Set<String> BLOCKED_COUNTRIES = new HashSet<>();A single accidental:
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:
public static final int MAX_BATCH_SIZE = 100;A consuming service is compiled.
Later the library changes:
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:
CommonConstants
ApplicationConstants
GlobalConstantsThese classes often become dumping grounds.
Using Meaningless Names
Bad:
VALUE
LIMIT
SIZE
TIMEOUT
DEFAULTwithout business context.
Making Every Constant Public
Most constants do not need application-wide visibility.
Assuming final Means Immutable
Bad:
public static final List<String> VALUES = new ArrayList<>();The list contents remain mutable.
Using Constants Instead of Enums
Bad:
public static final int ACTIVE = 1;
public static final int DISABLED = 2;Converting Every Literal Into a Constant
Bad:
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:
MAXIMUM_REFUND_DAYS = 30;even though it represents the same policy.
Exposing Mutable Arrays
Bad:
public static final String[] SUPPORTED_TYPES = {"A", "B"};Callers can modify array elements.
Ignoring Units
Bad:
private static final long RETRY_DELAY = 5000;Using Interface Constants
Example:
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.
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:
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:
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:
MAX_PAYMENT_AMOUNTwithout 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:
TEST_VALUE_1may 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:
if (attempts >= 3)and:
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
MAX_BATCH_SIZEcan 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:
MAX_FAILED_LOGIN_ATTEMPTS
ACCESS_TOKEN_TTL
PASSWORD_RESET_TOKEN_TTL
MINIMUM_PASSWORD_LENGTH
MAX_REQUESTS_PER_MINUTEThese values should have clear ownership and consistent usage.
Do Not Store Secrets as Constants
Bad:
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:
ROLE_ADMIN = 1
ROLE_USER = 2Prefer strongly typed security models.
Mutable Security Collections
This is dangerous:
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:
max-failed-login-attempts=0could accidentally lock every account immediately.
19. Testing Considerations
Constants that control business boundaries should be tested at their boundaries.
Suppose:
private static final int MAX_EXPORT_RECORDS = 10000;Test:
99991000010001
Test Meaning, Not Constant Implementation
Avoid writing tests whose only purpose is:
assertEquals(10000, MAX_EXPORT_RECORDS);unless the constant value itself is part of a public contract.
Instead, test behavior.
Example:
@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:
private static final int LIMIT = 100;Step 2: Determine Meaning
Ask what 100 actually represents.
Perhaps:
MAXIMUM_EXPORT_RECORDSStep 3: Determine Ownership
Should the value live in:
- Current service
- Domain policy
- Configuration class
- Enum
- Shared library
Step 4: Determine Correct Type
Instead of:
long TIMEOUT = 30000;consider:
Duration REQUEST_TIMEOUT = Duration.ofSeconds(30);Step 5: Restrict Visibility
Start with:
privateand 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 finalfor class-specific stable values. - Prefer immutable objects for shared constants.
- Prefer
Set.of,List.of, orMap.offor fixed collections. - Use enums for finite domain states.
- Use
Durationfor time-based values. - Use
BigDecimalfor 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:
public class AppConstants {
...
}when it collects unrelated values from every domain.
Interface Constants
Avoid:
public interface Constants {
int MAX_RETRY = 3;
}especially when classes implement the interface only to access constants.
Mutable Public Constants
Avoid:
public static final List<String> VALUES = new ArrayList<>();Meaningless Names
Avoid:
VALUE_1
NUMBER_30
LIMIT
DATAPublic by Default
Do not use:
public static finalunless other components genuinely require access.
Hardcoded Secrets
Never use constants for production credentials.
Raw Numeric States
Avoid:
STATUS_ACTIVE = 1
STATUS_DISABLED = 2when an enum communicates the domain better.
Raw Time Units
Avoid:
TIMEOUT = 30000without explicit meaning.
Constant Explosion
Do not create a constant for every small literal.
Using Constants to Hide Bad Design
Changing:
if (type == 1)to:
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
Durationcommunicate time more clearly? - Is
BigDecimalappropriate 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
- *Can we rename
LIMITto describe what is actually being limited? The current name does not communicate the business rule.*
- *This constant is only used inside this service. Please reduce its visibility from
publictoprivateunless there is a real external contract.*
- *
static finaldoes not make thisArrayListimmutable. Can we useList.ofor another immutable representation?*
- *This timeout is expressed as a raw integer. A
Durationwould make the unit and intent much clearer.*
- *These status constants model a fixed set of domain states. I think an enum would provide stronger type safety.*
- *Does this threshold need to vary between environments? If so, configuration may be a better owner than a Java constant.*
- *I would avoid adding another value to
CommonConstants. This one belongs specifically to the refund domain.*
- *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.*
- *Please avoid exposing this mutable array as a public constant. A caller can modify its elements globally.*
- *The value is extracted, but the name
VALUE_30still 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.
@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:
LIMITdoes not explain what is limited.
A better name is:
MAXIMUM_EXPORT_RECORDS2. WAIT_TIME Has No Unit
This:
60000appears to mean milliseconds.
The unit should be represented explicitly.
3. Numeric Strings Represent Domain States
These constants:
CSV = "1"
PDF = "2"represent export formats.
An enum is more appropriate.
4. Mutable Public Collection
This:
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
@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:
public enum ExportFormat {
CSV,
PDF
}Example request:
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:
@ConfigurationProperties(prefix = "report.export")
public record ReportExportProperties(
int maximumRecords,
Duration maximumWait) {
}Configuration:
report.export.maximum-records=10000
report.export.maximum-wait=1mWhy Each Change Is Useful
- Constant names communicate business purpose.
Durationeliminates time-unit ambiguity.ExportFormatremoves 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
finaland immutability?
A strong answer should explain that:
finalprevents reassignment of the reference.
It does not make the referenced object immutable.
For example:
final List<String> values = new ArrayList<>();still allows:
values.add("A");Java Library Interview
An advanced question may involve:
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:
CommonConstantswith 200 unrelated fields.
A senior candidate should recognize poor cohesion and weak domain ownership.
Code Review Interview
Candidates should distinguish:
static finalfrom 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:
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:
static finalThe 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:
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(30);over:
public static final int TIMEOUT = 30000;Prefer:
AccountStatus.ACTIVEover:
STATUS_ACTIVE = 1Prefer immutable collections over:
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:
VALUE
DATA
LIMIT
NUMBER_30
TYPE1
TYPE2Avoid 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.