1. Introduction
A magic number is a numeric value written directly inside code without a clear name explaining what the value represents.
For example:
if (order.getTotalAmount().compareTo(new BigDecimal("5000")) >= 0) {
order.setShippingCharge(BigDecimal.ZERO);
}The value 5000 may represent:
- A free-shipping threshold
- A premium-order threshold
- A promotional limit
- A regulatory limit
The code does not explain which one.
A developer reviewing the method must understand the surrounding business logic before knowing what the number means.
Magic numbers commonly appear in Java projects as:
- Discount percentages
- Retry counts
- Timeouts
- Maximum limits
- Minimum thresholds
- Pagination sizes
- Batch sizes
- File-size limits
- Age limits
- Interest rates
- Tax percentages
- Transaction limits
- Cache durations
- Password rules
- Lockout thresholds
Some numeric literals are naturally understandable.
For example:
count + 1The value 1 clearly means increment by one.
But this code:
if (failedAttempts >= 5) {
lockAccount();
}raises an important question:
Why five?
If 5 represents an account-lockout policy, it should be expressed using a meaningful name.
Avoiding magic numbers means replacing unexplained numeric business or technical values with named constants, enums, configuration properties, or dedicated value objects where appropriate.
The goal is not to replace every numeric literal.
The goal is to make important values understandable and maintainable.
2. What This Topic Means
In Java development, avoiding magic numbers means ensuring that important numeric values communicate their purpose directly through the code.
Consider:
if (customer.getLoyaltyPoints() >= 1000) {
applyDiscount(customer);
}A reviewer sees 1000, but the business meaning is unclear.
A better implementation is:
private static final int MINIMUM_LOYALTY_POINTS_FOR_DISCOUNT = 1000;Then:
if (customer.getLoyaltyPoints() >= MINIMUM_LOYALTY_POINTS_FOR_DISCOUNT) {
applyDiscount(customer);
}Now the condition reads like a business rule.
Magic numbers can appear in several forms.
Business Thresholds
if (orderAmount.compareTo(new BigDecimal("5000")) >= 0) {
...
}Timeouts
httpClient.setConnectTimeout(30000);Retry Counts
for (int attempt = 0; attempt < 3; attempt++) {
...
}Pagination
PageRequest.of(page, 50);Batch Processing
if (records.size() == 1000) {
flush();
}Security Rules
if (failedLoginAttempts >= 5) {
lockAccount();
}Percentage Calculations
amount.multiply(new BigDecimal("0.18"));Date-Based Business Rules
LocalDate.now().minusDays(30);The correct replacement depends on the nature of the value.
Possible approaches include:
static finalconstants- Configuration properties
- Enums
- Domain objects
- Dedicated policy classes
A reviewer should choose the simplest option that correctly represents the value.
3. Why It Matters in Real Projects
Readability
Compare:
if (failedAttempts >= 5) {
lockAccount();
}with:
if (failedAttempts >= MAX_FAILED_LOGIN_ATTEMPTS) {
lockAccount();
}The second version immediately explains what 5 means.
Developers should not need to remember undocumented values.
Maintainability
Business rules change.
Suppose a fraud service contains:
if (transactionAmount.compareTo(new BigDecimal("100000")) > 0) {
requireManualReview();
}If the threshold changes to 150000, developers must locate every copy of 100000.
If the value has a meaningful constant:
MANUAL_REVIEW_THRESHOLDthe change is easier and safer.
Debugging
Named values improve debugging because logs, conditions, and stack traces can be understood in business terms.
Instead of asking:
Why did this fail at 30?
a developer sees:
PAYMENT_EXPIRY_DAYSand understands what the value controls.
Reliability
Hardcoded values copied across several methods can become inconsistent.
One service may use:
5000while another accidentally uses:
5500for the same business rule.
Team Development
Named constants communicate domain knowledge to other developers.
A junior developer reading:
MAXIMUM_REFUND_WINDOW_DAYSunderstands the rule immediately.
Performance
Magic numbers are not inherently a performance problem.
However, technical magic numbers such as batch size, timeout, connection pool size, or cache duration can affect performance significantly.
The concern is not that the number is literal.
The concern is that an unexplained value may be difficult to tune correctly.
4. Core Concept
The core idea is:
Important numeric values should communicate their meaning through names or configuration.
Consider:
if (order.getTotalAmount().compareTo(new BigDecimal("5000")) >= 0) {
return BigDecimal.ZERO;
}
return new BigDecimal("200");There are two important values:
5000200
Their meaning is not obvious.
A better implementation is:
private static final BigDecimal FREE_SHIPPING_THRESHOLD = new BigDecimal("5000.00");
private static final BigDecimal STANDARD_SHIPPING_CHARGE = new BigDecimal("200.00");Then:
if (order.getTotalAmount().compareTo(FREE_SHIPPING_THRESHOLD) >= 0) {
return BigDecimal.ZERO;
}
return STANDARD_SHIPPING_CHARGE;The method now documents the rule.
Java Constant Convention
Java constants are normally declared using:
private static finaland named using uppercase snake case.
Example:
private static final int MAX_RETRY_ATTEMPTS = 3;Constants vs Configuration
Not every magic number should become a Java constant.
If the business expects a value to change without rebuilding the application, configuration is often more appropriate.
For example:
payment.max-retry-attempts=3may be better than:
private static final int MAX_RETRY_ATTEMPTS = 3;depending on operational requirements.
Constants vs Enums
If a number represents a domain state, an enum may be more appropriate than a numeric constant.
Bad:
if (customer.getType() == 2) {
...
}Better:
if (customer.getType() == CustomerType.PREMIUM) {
...
}The reviewer should focus on meaning, not simply replacing numbers mechanically.
5. Important Rules
- Extract important business thresholds into well-named constants.
- Use configuration when values should change without code deployment.
- Use enums when numbers represent categories or states.
- Do not replace obvious values such as
0,1, or2when their meaning is naturally clear. - Do not create constants named after the number itself.
- Prefer business-oriented names.
- Avoid duplicating the same numeric threshold across multiple classes.
- Use
BigDecimalfor important monetary calculations instead ofdouble. - Give units to technical constants where ambiguity is possible.
- Prefer names such as
TIMEOUT_SECONDSrather than simplyTIMEOUT. - Avoid mixing milliseconds, seconds, and minutes without explicit naming.
- Centralize shared business rules when they represent one authoritative policy.
- Do not place unrelated constants inside generic utility classes.
- Avoid exposing implementation-specific numbers directly throughout controllers and services.
- Test boundary values around important numeric thresholds.
- Review whether a value belongs in source code, configuration, or a domain policy.
Bad:
private static final int FIVE = 5;Better:
private static final int MAX_FAILED_LOGIN_ATTEMPTS = 5;6. Bad Code Example
Consider a Spring Boot payment service.
@Service
public class PaymentService {
private final PaymentRepository paymentRepository;
private final FraudClient fraudClient;
public PaymentService(PaymentRepository paymentRepository, FraudClient fraudClient) {
this.paymentRepository = paymentRepository;
this.fraudClient = fraudClient;
}
public Payment processPayment(PaymentRequest request) {
if (request.getAmount().compareTo(new BigDecimal("100000")) > 0) {
throw new IllegalArgumentException("Payment amount exceeds limit");
}
int attempts = 0;
while (attempts < 3) {
try {
FraudDecision decision = fraudClient.check(request);
if (decision.getRiskScore() > 80) {
throw new IllegalStateException("Payment requires manual review");
}
Payment payment = new Payment();
payment.setCustomerId(request.getCustomerId());
payment.setAmount(request.getAmount());
payment.setProcessingFee(request.getAmount().multiply(new BigDecimal("0.02")));
payment.setStatus(PaymentStatus.COMPLETED);
return paymentRepository.save(payment);
} catch (FraudServiceUnavailableException ex) {
attempts++;
if (attempts == 3) {
throw ex;
}
try {
Thread.sleep(2000);
} catch (InterruptedException interruptedException) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Retry interrupted", interruptedException);
}
}
}
throw new IllegalStateException("Payment processing failed");
}
}This code contains several numeric values with important business or technical meaning.
7. Problems in the Bad Code
Payment Limit Is Unclear
This value:
100000represents an important payment rule.
But the reader must infer its meaning from the exception message.
Retry Count Is Repeated
The number 3 appears in:
while (attempts < 3)and:
if (attempts == 3)If one is changed and the other is not, retry behavior becomes inconsistent.
Fraud Threshold Is Hardcoded
This condition:
decision.getRiskScore() > 80does not explain why 80 is important.
Processing Fee Is Hidden
This calculation:
request.getAmount().multiply(new BigDecimal("0.02"))contains a business fee rate without a name.
Retry Delay Has No Unit
This call:
Thread.sleep(2000);uses milliseconds, but the value does not communicate that clearly.
Maintainability Risk
Any future change requires editing implementation code directly.
Configuration Opportunity
Values such as:
- Maximum payment amount
- Fraud threshold
- Retry attempts
- Retry delay
may need operational configuration rather than hardcoded constants.
Testing Is Harder to Understand
A test using 80, 100000, or 3 must know the same undocumented assumptions.
8. Code Review Findings
A senior reviewer should identify:
100000appears to represent a maximum payment threshold.3controls retry behavior and is duplicated.80represents the fraud/manual-review threshold.0.02represents a processing fee.2000is a retry delay expressed in milliseconds.- Each value should have a meaningful name.
- Operational values may belong in configuration rather than Java constants.
- Money values should remain
BigDecimal. - Units should be explicit in names.
- Tests should cover the exact boundaries around these thresholds.
- Retry behavior might be better implemented through an existing retry mechanism if the project already uses one, but a new framework should not be introduced solely to remove magic numbers.
The reviewer should also determine whether all values have the same ownership.
For example:
- Fee rate may be a business-policy value.
- Retry delay may be an infrastructure value.
- Fraud threshold may belong to fraud configuration.
They should not automatically be placed in one generic constants class.
9. Reviewer Comment Example
100000looks like a business payment limit. Could we give this threshold a meaningful name or move it to configuration if operations need to change it without deployment?
The retry count
3appears in more than one place. Please centralize it so both checks cannot diverge.
Can we name the fraud threshold rather than comparing directly with
80? That would make the review rule easier to understand.
0.02appears to be the processing-fee rate. Please extract it into a business-oriented constant or configuration property.
Thread.sleep(2000)hides the time unit. Please make the retry delay explicit, for exampleRETRY_DELAY_MILLIS, or use aDuration.
I would avoid moving all of these values into a generic
Constantsclass. They belong to different business and infrastructure concerns.
10. Improved Code
@Service
public class PaymentService {
private static final BigDecimal MAXIMUM_PAYMENT_AMOUNT = new BigDecimal("100000.00");
private static final BigDecimal PROCESSING_FEE_RATE = new BigDecimal("0.02");
private static final int MANUAL_REVIEW_RISK_SCORE = 80;
private static final int MAX_FRAUD_CHECK_ATTEMPTS = 3;
private static final Duration FRAUD_RETRY_DELAY = Duration.ofSeconds(2);
private final PaymentRepository paymentRepository;
private final FraudClient fraudClient;
public PaymentService(PaymentRepository paymentRepository, FraudClient fraudClient) {
this.paymentRepository = paymentRepository;
this.fraudClient = fraudClient;
}
public Payment processPayment(PaymentRequest request) {
validatePaymentAmount(request.getAmount());
int attempts = 0;
while (attempts < MAX_FRAUD_CHECK_ATTEMPTS) {
try {
FraudDecision decision = fraudClient.check(request);
validateFraudDecision(decision);
Payment payment = createPayment(request);
return paymentRepository.save(payment);
} catch (FraudServiceUnavailableException ex) {
attempts++;
if (attempts >= MAX_FRAUD_CHECK_ATTEMPTS) {
throw ex;
}
waitBeforeRetry();
}
}
throw new IllegalStateException("Payment processing failed");
}
private void validatePaymentAmount(BigDecimal amount) {
if (amount.compareTo(MAXIMUM_PAYMENT_AMOUNT) > 0) {
throw new IllegalArgumentException("Payment amount exceeds limit");
}
}
private void validateFraudDecision(FraudDecision decision) {
if (decision.getRiskScore() > MANUAL_REVIEW_RISK_SCORE) {
throw new IllegalStateException("Payment requires manual review");
}
}
private Payment createPayment(PaymentRequest request) {
Payment payment = new Payment();
payment.setCustomerId(request.getCustomerId());
payment.setAmount(request.getAmount());
payment.setProcessingFee(request.getAmount().multiply(PROCESSING_FEE_RATE));
payment.setStatus(PaymentStatus.COMPLETED);
return payment;
}
private void waitBeforeRetry() {
try {
Thread.sleep(FRAUD_RETRY_DELAY.toMillis());
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Retry interrupted", ex);
}
}
}If the values should be configurable:
@ConfigurationProperties(prefix = "payment")
public record PaymentProperties(
BigDecimal maximumAmount,
BigDecimal processingFeeRate,
int manualReviewRiskScore,
int maxFraudCheckAttempts,
Duration fraudRetryDelay) {
}Example configuration:
payment.maximum-amount=100000.00
payment.processing-fee-rate=0.02
payment.manual-review-risk-score=80
payment.max-fraud-check-attempts=3
payment.fraud-retry-delay=2s11. Improved Code Explanation
Maximum Payment Amount Is Named
Instead of:
100000the code uses:
MAXIMUM_PAYMENT_AMOUNTThe condition now communicates the business rule.
Processing Fee Has Meaning
The value:
0.02is represented as:
PROCESSING_FEE_RATEA developer immediately knows that it is a percentage-like fee rate.
Fraud Threshold Is Explicit
Instead of:
riskScore > 80the code uses:
riskScore > MANUAL_REVIEW_RISK_SCORERetry Count Has One Source of Truth
Both retry conditions reference:
MAX_FRAUD_CHECK_ATTEMPTSChanging retry policy requires one modification.
Duration Represents Time Clearly
Instead of:
2000the code uses:
Duration.ofSeconds(2)This makes the unit explicit and reduces unit-conversion mistakes.
Configuration Is an Option
If operations or product teams frequently change thresholds, configuration properties may be more appropriate than static final constants.
12. Bad Code vs Improved Code
| Aspect | Bad Code | Improved Code |
|---|---|---|
| Readability | Numbers require interpretation | Names explain business meaning |
| Maintainability | Values changed directly in logic | Values centralized |
| Reliability | Repeated literals can diverge | One authoritative value |
| Testability | Test values depend on hidden assumptions | Boundaries map to named rules |
| Configuration | Redeployment required for every change | Selected values can be externalized |
| Time units | 2000 is ambiguous | Duration makes unit explicit |
| PR Review | Reviewer must infer numeric meaning | Reviewer can review business intent directly |
13. Real Project Scenario
Consider a healthcare appointment platform.
A scheduling service contains:
if (appointmentDate.isBefore(LocalDate.now().plusDays(2))) {
throw new IllegalArgumentException("Appointment must be booked in advance");
}
if (patient.getMissedAppointments() >= 3) {
requireManualApproval();
}
if (consultationDuration > 60) {
chargeExtendedConsultationFee();
}The numbers 2, 3, and 60 represent different business policies:
- Minimum booking lead time
- Missed-appointment threshold
- Maximum standard consultation duration
Months later, the business changes:
- Booking lead time from 2 days to 1 day
- Manual review threshold from 3 missed appointments to 2
- Standard consultation duration from 60 minutes to 45
If the values are scattered across:
- REST controllers
- Services
- Batch jobs
- Notification logic
the change becomes risky.
A better design uses explicit policy values:
private static final int MINIMUM_BOOKING_LEAD_DAYS = 2;
private static final int MAX_MISSED_APPOINTMENTS_WITHOUT_REVIEW = 3;
private static final Duration STANDARD_CONSULTATION_DURATION = Duration.ofMinutes(60);If product teams need to adjust these values frequently, they may belong in configuration or a policy service.
14. Production Impact
Magic numbers can create production issues when they represent critical rules.
Inconsistent Business Results
If the same threshold is duplicated across services, one copy may be updated while another remains unchanged.
Example:
- API accepts payments up to ₹1,50,000.
- Batch validation still rejects anything above ₹1,00,000.
Incorrect Boundary Behavior
A developer may not understand whether:
100represents:
- Maximum inclusive value
- Maximum exclusive value
- Percentage
- Currency amount
- Number of records
This can cause incorrect comparisons.
Timeout Misconfiguration
Using:
30000without a unit can lead to confusion between:
- Milliseconds
- Seconds
- Microseconds
Wrong Financial Calculations
A fee or tax rate such as:
0.18may be duplicated and changed inconsistently.
Difficult Production Support
Logs and code reviews become harder when engineers must decode numeric values during incidents.
Deployment Dependency
If frequently changing operational values are hardcoded, even minor adjustments require:
- Code change
- Pull Request
- Build
- Deployment
External configuration may reduce that operational burden where appropriate.
15. Common Developer Mistakes
Extracting Every Number
Developers sometimes create:
private static final int ZERO = 0;
private static final int ONE = 1;This usually adds noise rather than meaning.
Poor Constant Names
Bad:
private static final int LIMIT = 5;Better:
private static final int MAX_FAILED_LOGIN_ATTEMPTS = 5;Generic Constant Classes
Example:
public class Constants {
public static final int FIVE = 5;
public static final int THIRTY = 30;
public static final BigDecimal RATE = new BigDecimal("0.18");
}This removes numeric literals but does not add business meaning.
Copying Constants Across Classes
Two services define different constants for the same business rule.
Hardcoding Time in Milliseconds
Example:
Thread.sleep(60000);The unit is easy to misread.
Using double for Money
Bad:
double taxRate = 0.18;For monetary calculations, BigDecimal is usually safer.
Putting Everything in Configuration
Not every constant needs to be externally configurable.
Some values are stable implementation details.
Ignoring Units
Bad:
private static final int TIMEOUT = 30;Better:
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(30);Numeric Status Codes Inside Business Logic
Bad:
if (customer.getStatus() == 2) {
...
}Use an enum or meaningful type.
16. Edge Cases
Naturally Obvious Numbers
This code is usually fine:
for (int index = 0; index < items.size(); index++) {
...
}Extracting 0 into:
START_INDEXwould not improve readability.
Mathematical Constants
Values used in domain formulas may sometimes be naturally understood but still deserve names if business-specific.
Percentage Representation
A rate may be represented as:
0.18or:
18The naming should make the representation clear.
Examples:
TAX_RATEor:
TAX_PERCENTAGEUnits
A value such as:
30could mean:
- 30 milliseconds
- 30 seconds
- 30 days
- 30 records
Use types like Duration when appropriate.
Currency
A threshold may differ by currency.
One global constant may be incorrect if the application supports multiple currencies.
Regional Business Rules
Legal age or transaction limits may differ by jurisdiction.
A single hardcoded constant may not be enough.
Feature-Specific Configuration
A threshold may intentionally vary across environments.
For example, test and production rate limits may be different.
Integer Overflow
Large numeric constants should use appropriate types such as long.
17. Performance Considerations
Avoiding magic numbers generally has no meaningful runtime performance cost.
Using:
MAX_RETRY_ATTEMPTSinstead of:
3does not materially affect performance.
Java compile-time constants are highly optimized.
However, the values themselves may affect runtime behavior.
Batch Size
BATCH_SIZEcan influence:
- Memory usage
- Database round trips
- Processing throughput
Timeout
REQUEST_TIMEOUTcan affect:
- Thread utilization
- User latency
- Downstream pressure
Retry Count
MAX_RETRY_ATTEMPTScan affect:
- Downstream traffic
- Recovery behavior
- Failure amplification
Connection Pool Size
A numeric pool setting directly affects concurrency and resource consumption.
Page Size
Large page sizes may increase memory use and response time.
Therefore, reviewers should ask whether technical numeric values are:
- Named
- Correctly scoped
- Properly configured
- Tunable where necessary
The naming change itself is not a performance optimization.
18. Security Considerations
Magic numbers are not inherently security vulnerabilities.
However, important security thresholds should be explicit.
Login Attempt Limits
Bad:
if (failedAttempts >= 5) {
lockAccount();
}Better:
if (failedAttempts >= MAX_FAILED_LOGIN_ATTEMPTS) {
lockAccount();
}Token Expiration
Bad:
expiration = now.plusSeconds(3600);Better:
expiration = now.plus(ACCESS_TOKEN_TTL);Password Length
Bad:
if (password.length() < 8) {
...
}Better:
if (password.length() < MINIMUM_PASSWORD_LENGTH) {
...
}Rate Limits
Values controlling requests per minute should not be scattered across endpoints.
Security Configuration
Some security limits may be better externalized into controlled configuration.
However, allowing arbitrary runtime modification of security policies may itself require governance.
Reviewer Responsibility
The reviewer should understand the purpose of security-related numbers and ensure they are not accidentally duplicated or inconsistent.
19. Testing Considerations
Important numeric values usually create boundaries.
Boundary testing is therefore especially important.
Suppose:
private static final BigDecimal MAXIMUM_PAYMENT_AMOUNT = new BigDecimal("100000.00");Test:
99999.99100000.00100000.01
Example:
@Test
void shouldAllowPaymentAtMaximumAmount() {
PaymentRequest request = requestWithAmount(new BigDecimal("100000.00"));
assertDoesNotThrow(() -> paymentService.processPayment(request));
}And:
@Test
void shouldRejectPaymentAboveMaximumAmount() {
PaymentRequest request = requestWithAmount(new BigDecimal("100000.01"));
assertThrows(
IllegalArgumentException.class,
() -> paymentService.processPayment(request));
}Retry Tests
Verify:
- First attempt succeeds
- Second attempt succeeds
- Maximum retry count reached
- No additional attempt after the maximum
Risk Threshold Tests
Test:
- Score below threshold
- Score exactly at threshold
- Score above threshold
Fee Tests
Verify monetary calculations using precise BigDecimal assertions.
Configuration Tests
If values come from configuration, verify property binding.
Unit Tests Should Express Meaning
Tests should preferably use the same domain terminology as production code rather than unexplained literals.
20. Refactoring Guidelines
Step 1: Identify Important Numeric Literals
Search business and infrastructure code for numeric values that represent rules.
Step 2: Understand Their Meaning
Do not extract a number before understanding what it represents.
Step 3: Check for Duplication
Search the project for the same or equivalent value.
Step 4: Determine Ownership
Ask whether the value belongs to:
- The current class
- A domain policy
- Configuration
- An enum
- Infrastructure settings
Step 5: Introduce a Meaningful Name
Before:
if (attempts >= 3) {
...
}After:
if (attempts >= MAX_RETRY_ATTEMPTS) {
...
}Step 6: Preserve Behavior
Do not change the number while refactoring unless the business requirement also changes.
Step 7: Add Boundary Tests
Verify behavior immediately below, at, and above the threshold.
Step 8: Replace All Authoritative Copies
If several locations implement the same rule, centralize them carefully.
Step 9: Externalize Only When Necessary
Do not turn every number into a configuration property.
Step 10: Review Naming
A constant name should explain the rule without requiring the developer to inspect its value.
21. Best Practices
- Name numeric business rules explicitly.
- Keep constants close to the domain they belong to.
- Use
private static finalfor class-level constants when appropriate. - Use configuration for operationally adjustable values.
- Use enums instead of numeric state codes.
- Use
Durationfor time-based values. - Use
BigDecimalfor precise monetary rules. - Include units in names when types cannot express them.
- Avoid duplicating thresholds across services.
- Test numeric boundaries.
- Keep constants cohesive.
- Avoid generic constants classes.
- Keep one source of truth for shared business values.
- Document unusual formulas when naming alone is insufficient.
- Prefer types that encode meaning over primitive numbers where practical.
22. Practices to Avoid
Unexplained Business Numbers
Avoid:
if (age >= 18) {
...
}when 18 represents a business or legal eligibility rule used in multiple places.
Constants Named After Values
Avoid:
private static final int THIRTY = 30;Generic Names
Avoid:
private static final int LIMIT = 10;Large Common Constants Classes
Avoid unrelated values such as:
PAYMENT_LIMIT
MAX_LOGIN_ATTEMPTS
ORDER_PAGE_SIZE
CACHE_TTLinside one global class solely for reuse.
Hardcoded Milliseconds
Avoid:
Thread.sleep(5000);when a Duration would communicate intent.
Numeric Domain States
Avoid:
status == 1for business states.
Duplicate Thresholds
Avoid defining the same business value in multiple services.
Premature Configuration
Do not make stable internal values externally configurable without a genuine operational need.
Imprecise Money Calculations
Avoid using double where financial precision matters.
23. Code Review Checklist
- Does this numeric literal have an obvious meaning?
- Does the number represent a business rule?
- Does the number represent an infrastructure setting?
- Would a named constant improve readability?
- Should the value be configurable?
- Is the same numeric value duplicated elsewhere?
- Does the same number represent the same concept everywhere?
- Is the constant name business-oriented?
- Does the name include a unit where needed?
- Would
Durationbe clearer than a raw integer? - Should this numeric state be replaced by an enum?
- Is
BigDecimalbeing used for monetary values? - Is the business threshold owned by the correct class or component?
- Are unrelated constants being placed in a generic constants class?
- Are boundary values covered by tests?
- Is the threshold inclusive or exclusive?
- Is the configured value validated?
- Could different regions or currencies require different values?
- Is this value expected to change frequently?
- Would external configuration reduce unnecessary deployments?
- Does the refactoring preserve existing business behavior?
- Is the resulting code easier to understand than the original?
24. Common Pull Request Review Comments
- *What does
5000represent here? If this is the free-shipping threshold, please give it a business-oriented name.*
- *The retry count
3appears in multiple conditions. Can we centralize it so the retry policy has one source of truth?*
- *Please avoid the raw
30000timeout. ADurationwould make the unit and intent clearer.*
- *
0.18appears to be the tax rate. Please extract it into the appropriate pricing or tax policy rather than leaving the value inline.*
- *This value looks operational rather than fixed business logic. Should it come from configuration instead of being hardcoded?*
- *I would avoid adding this to the global
Constantsclass. The value belongs specifically to payment processing.*
- *Can we add boundary tests around this threshold? We should verify behavior below, exactly at, and above the limit.*
- *The status comparison uses
2. Could we model this as an enum so the code communicates the actual state?*
- *The name
LIMITis too generic. Please use a name that describes what is being limited.*
- *This monetary calculation uses
double. Since this is a financial rule, please considerBigDecimalwith an explicit rate.*
25. Code Review Exercise
Review the following Spring Boot account service.
Identify:
- Problems
- Code smells
- Risks
- Improvements
Do not read the solution until completing your review.
@Service
public class AccountService {
private final AccountRepository accountRepository;
private final NotificationService notificationService;
public AccountService(AccountRepository accountRepository, NotificationService notificationService) {
this.accountRepository = accountRepository;
this.notificationService = notificationService;
}
public void recordFailedLogin(Long accountId) {
Account account = accountRepository.findById(accountId)
.orElseThrow(() -> new IllegalArgumentException("Account not found"));
int attempts = account.getFailedLoginAttempts() + 1;
account.setFailedLoginAttempts(attempts);
if (attempts >= 5) {
account.setLocked(true);
account.setLockedUntil(Instant.now().plusSeconds(1800));
}
accountRepository.save(account);
if (attempts == 3) {
notificationService.sendSecurityWarning(account.getUserId());
}
}
public boolean canResetPassword(Account account) {
return account.getPasswordAgeDays() >= 90
|| account.getFailedLoginAttempts() >= 5;
}
}Review the code for:
- Repeated thresholds
- Security-policy values
- Time units
- Naming
- Boundary behavior
- Policy ownership
- Configuration opportunities
26. Exercise Solution
Problems Identified
1. Account Lockout Threshold Is Hardcoded
The value:
5controls account locking.
It is a security policy and should have an explicit name.
2. Same Threshold Is Repeated
The value 5 appears in:
attempts >= 5and:
account.getFailedLoginAttempts() >= 5If one changes, the other may not.
3. Warning Threshold Is Unclear
The value:
3represents the point at which a warning is sent.
4. Lock Duration Is a Magic Number
This:
1800means 30 minutes in seconds.
The code does not make that obvious.
5. Password Age Is Hardcoded
The value:
90represents a password-age policy.
6. Security Policies Are Mixed Into Implementation
These values may need centralized security-policy ownership.
Improved Code
@Service
public class AccountService {
private static final int MAX_FAILED_LOGIN_ATTEMPTS = 5;
private static final int SECURITY_WARNING_ATTEMPT_THRESHOLD = 3;
private static final int MAX_PASSWORD_AGE_DAYS = 90;
private static final Duration ACCOUNT_LOCK_DURATION = Duration.ofMinutes(30);
private final AccountRepository accountRepository;
private final NotificationService notificationService;
private final Clock clock;
public AccountService(AccountRepository accountRepository, NotificationService notificationService, Clock clock) {
this.accountRepository = accountRepository;
this.notificationService = notificationService;
this.clock = clock;
}
public void recordFailedLogin(Long accountId) {
Account account = accountRepository.findById(accountId)
.orElseThrow(() -> new IllegalArgumentException("Account not found"));
int attempts = account.getFailedLoginAttempts() + 1;
account.setFailedLoginAttempts(attempts);
if (attempts >= MAX_FAILED_LOGIN_ATTEMPTS) {
lockAccount(account);
}
accountRepository.save(account);
if (attempts == SECURITY_WARNING_ATTEMPT_THRESHOLD) {
notificationService.sendSecurityWarning(account.getUserId());
}
}
public boolean canResetPassword(Account account) {
return account.getPasswordAgeDays() >= MAX_PASSWORD_AGE_DAYS
|| account.getFailedLoginAttempts() >= MAX_FAILED_LOGIN_ATTEMPTS;
}
private void lockAccount(Account account) {
account.setLocked(true);
account.setLockedUntil(Instant.now(clock).plus(ACCOUNT_LOCK_DURATION));
}
}Why Each Change Is Useful
- Security thresholds now have meaningful names.
- Lockout policy has one source of truth.
Durationcommunicates the lock period directly.- Password-age policy is understandable without inspecting the number.
- Injecting
Clockmakes time-based tests easier. - A policy change no longer requires searching for unexplained literals.
Configuration Option
If security administrators must modify these values without deployment, they may belong in configuration:
@ConfigurationProperties(prefix = "security.account")
public record AccountSecurityProperties(
int maxFailedLoginAttempts,
int warningAttemptThreshold,
int maxPasswordAgeDays,
Duration lockDuration) {
}However, externalizing security policy should be intentional and controlled.
27. Interview Perspective
Magic-number questions are common in Java code-review interviews because they reveal whether a candidate understands maintainability beyond syntax.
Java Interview
A candidate may receive:
if (attempts >= 5) {
lock();
}and be asked how it should be improved.
A basic answer is:
Create a constant.
A stronger answer is:
First determine what
5represents. If it is the maximum failed-login threshold, use a meaningful constant such asMAX_FAILED_LOGIN_ATTEMPTS. If it needs operational tuning, consider configuration.
Spring Boot Interview
The interviewer may ask:
Should every business number become an
application.propertiesvalue?
A strong candidate should say no.
Configuration is useful when values need:
- Environment-specific behavior
- Runtime operational tuning
- Product configuration without rebuilding
Stable implementation constants may remain in code.
Senior Developer Interview
A senior developer may be asked where constants should live.
The correct answer depends on ownership.
Avoid putting everything inside:
CommonConstantsInstead, keep values near:
- Pricing policy
- Security policy
- Payment configuration
- Order rules
- Infrastructure settings
Code Review Interview
Candidates may be expected to recognize that numeric literals are not automatically wrong.
For example:
index + 1does not need:
INDEX_INCREMENTGood review judgment matters more than mechanically removing every number.
28. Interview Questions and Answers
Basic Question
Question: What is a magic number in Java?
Answer:
A magic number is a numeric literal used directly in code without clearly communicating its meaning. For example, failedAttempts >= 5 is harder to understand than failedAttempts >= MAX_FAILED_LOGIN_ATTEMPTS.
Intermediate Question
Question: Should every numeric literal be converted into a constant?
Answer:
No. Values such as 0 and 1 are often naturally understandable in loops, increments, and comparisons. A numeric literal should usually be extracted when it represents business knowledge, a technical policy, a threshold, a timeout, a percentage, or another value whose meaning is not obvious.
Advanced Question
Question: When should a magic number become configuration instead of a Java constant?
Answer:
Use configuration when the value is expected to vary by environment, change operationally, or be modified without rebuilding the application. Use a Java constant when the value is stable and naturally belongs to the implementation or domain code. The decision should be based on ownership and change frequency.
Scenario-Based Question
Question: A transaction limit of 100000 appears in three microservices. What would you do?
Answer:
First, I would determine whether all three values represent the same business rule. If they do, the rule needs one authoritative ownership model. Depending on the architecture, that might be a domain service, central configuration, policy service, or shared domain contract. I would not simply create three constants with the same value because that still duplicates business knowledge.
Code-Review Question
Question: What would you comment on this code?
Thread.sleep(30000);Answer:
I would ask for a meaningful time representation, preferably a Duration, because 30000 hides the unit and intent. For example, RETRY_DELAY = Duration.ofSeconds(30) communicates the purpose clearly.
Real-Project Question
Question: How have you handled magic numbers in financial applications?
Answer:
I separate stable implementation values from business-configurable values. Monetary thresholds and rates are represented using BigDecimal, named according to the actual business rule, and tested at boundaries. Values that product or operations teams need to adjust are externalized into validated configuration or policy components rather than being scattered across services.
29. Quick Rule to Remember
If a reviewer has to ask “What does this number mean?”, the number probably needs a name.
30. Final Takeaway
What the Developer Should Remember
Magic numbers hide important information.
The problem is not that numeric literals exist.
The problem is that important numbers appear without explaining their purpose.
Instead of:
if (attempts >= 5) {
...
}prefer:
if (attempts >= MAX_FAILED_LOGIN_ATTEMPTS) {
...
}Developers should:
- Name business thresholds.
- Name technical limits.
- Use configuration when values need operational control.
- Use
Durationfor time-related values. - Use
BigDecimalfor monetary calculations. - Use enums instead of numeric state codes.
- Keep constants near their business owner.
- Avoid generic constants classes.
- Test important boundaries.
- Avoid extracting naturally obvious numbers unnecessarily.
What the Reviewer Should Check
During Pull Request review, ask:
- What does this number represent?
- Is the meaning obvious?
- Is the value duplicated?
- Is it a business rule?
- Is it an operational setting?
- Should it be configurable?
- Does the name describe the rule?
- Are units explicit?
- Is money represented precisely?
- Is an enum more appropriate?
- Are boundary values tested?
- Does this rule have one authoritative owner?
What Should Be Avoided in Production Code
Avoid unexplained values such as:
3
5
30
80
1000
5000
0.02when they control important application behavior.
Avoid replacing them with meaningless constants such as:
THREE
FIVE
VALUE_30
DEFAULT_NUMBERAvoid putting unrelated constants into one global utility class.
Avoid hardcoding frequently changing operational settings when configuration is more appropriate.
Avoid assuming that every numeric literal is a code smell.
The production-quality approach is:
Give important numbers meaningful business or technical names, put them in the correct ownership boundary, and make every significant threshold understandable without forcing the next developer to guess.