1. Introduction
String comparison is one of the most common operations in Java applications.
Developers compare strings while working with:
- Request parameters
- Database values
- Status fields
- User roles
- Payment types
- Configuration values
- External API responses
- File data
- Headers
- Business codes
- Validation rules
A very common Java mistake is comparing strings using == instead of equals().
For example:
if (paymentStatus == "SUCCESS") {
processOrder();
}This code may sometimes appear to work because of Java's String pool.
However, == compares object references, not string contents.
If the value comes from:
- Database
- HTTP request
- JSON deserialization
- External API
- File
new String(...)- Runtime transformation
the comparison may return false even when both strings contain exactly the same characters.
Correct string comparison usually requires:
paymentStatus.equals("SUCCESS");or, when the variable may be null:
"SUCCESS".equals(paymentStatus);Understanding this distinction is essential during Java code reviews because incorrect string comparison can produce subtle production bugs that may not appear in local testing.
2. What This Topic Means
Java strings are objects.
When two strings are compared using:
==Java checks whether both variables reference the same object.
When strings are compared using:
equals()Java checks whether both strings contain the same sequence of characters.
Consider:
String first = new String("ACTIVE");
String second = new String("ACTIVE");The following comparison checks references:
first == secondThe result is:
falsebecause two separate String objects were created.
However:
first.equals(second)returns:
truebecause both objects contain the same characters.
In production code, developers usually care about value equality rather than object identity.
For example:
if ("ACTIVE".equals(account.getStatus())) {
activateServices();
}The important question is:
"Does the account status contain the value ACTIVE?"
not:
"Does the account status reference exactly the same String object as this literal?"
3. Why It Matters in Real Projects
Reliability
Incorrect string comparison can cause valid business conditions to fail unexpectedly.
For example:
if (paymentResponse.getStatus() == "SUCCESS") {
confirmOrder();
}If the status comes from JSON deserialization, the reference may differ from the string literal.
The payment can succeed, but the order may never be confirmed.
Debugging
This bug is particularly confusing because logging may show:
paymentStatus = SUCCESSand:
expectedStatus = SUCCESSyet:
paymentStatus == expectedStatusreturns false.
Developers may spend significant time investigating API or database problems when the actual problem is reference comparison.
Maintainability
Using the correct comparison operation makes intent clear.
"ACTIVE".equals(status)clearly means value comparison.
Using:
status == "ACTIVE"introduces ambiguity and relies on implementation behavior that should not determine business logic.
Team Development
Consistent use of equals() prevents developers from depending on String pool behavior.
Code reviews should reject == for normal string-content comparison.
Security
String comparison can affect security-sensitive logic.
Examples include:
- Role checks
- Authorization codes
- Verification statuses
- Token types
- Access levels
Incorrect comparison can lead to authorization logic behaving differently than expected.
4. Core Concept
Java provides two fundamentally different comparison mechanisms.
Reference Comparison with ==
Example:
String first = new String("ADMIN");
String second = new String("ADMIN");
System.out.println(first == second);Output:
false== checks whether:
first reference == second referenceIt does not compare the characters.
Content Comparison with equals()
Example:
System.out.println(first.equals(second));Output:
trueString.equals() compares the content of both strings.
Conceptually, it checks whether both strings contain the same sequence of characters.
String Pool Behavior
Java stores string literals in the String pool.
For example:
String first = "ACTIVE";
String second = "ACTIVE";Both variables usually refer to the same pooled string object.
Therefore:
first == secondmay return:
trueThis is why developers sometimes incorrectly believe == is valid for Strings.
Now consider:
String first = "ACTIVE";
String second = new String("ACTIVE");Then:
first == secondreturns:
falsewhile:
first.equals(second)returns:
trueProduction code should not rely on whether values happen to reference the same pooled String.
Case Sensitivity
equals() is case-sensitive.
"ACTIVE".equals("active")returns:
falseIf the business requirement explicitly says case should be ignored, use:
"ACTIVE".equalsIgnoreCase(status)Do not use equalsIgnoreCase() automatically.
The comparison method must match the actual business rule.
5. Important Rules
- Use
equals()for normal String content comparison. - Do not use
==to compare String values. - Use a constant or literal on the left side when the variable may be
null. - Prefer
"ACTIVE".equals(status)overstatus.equals("ACTIVE")when null is possible. - Use
Objects.equals(first, second)when both values may benull. - Use
equalsIgnoreCase()only when business rules explicitly allow case-insensitive matching. - Do not use
intern()as a replacement for proper String comparison. - Do not assume database or HTTP values belong to the String pool.
- Do not assume identical console output means
==will returntrue. - Handle whitespace separately if business rules require trimming.
- Do not silently call
trim()unless whitespace should genuinely be ignored. - Prefer enums when a field represents a fixed set of business states.
- Avoid repeated string literals for important domain values.
- Centralize important constants where appropriate.
- Review String comparisons carefully in authorization and workflow logic.
- Test values created dynamically, not only compile-time literals.
6. Bad Code Example
Consider a Spring Boot payment-processing service.
import org.springframework.stereotype.Service;
@Service
public class PaymentStatusService {
public PaymentResult process(
PaymentGatewayResponse response) {
String status = response.getStatus();
if (status == "SUCCESS") {
return new PaymentResult(
true,
"Payment completed"
);
}
if (status == "FAILED") {
return new PaymentResult(
false,
"Payment failed"
);
}
return new PaymentResult(
false,
"Unknown payment status"
);
}
}Assume the external payment API returns JSON:
{
"status": "SUCCESS"
}Jackson deserializes the JSON into:
PaymentGatewayResponseThe value may contain the text:
SUCCESSbut it is not guaranteed to be the exact same object as the "SUCCESS" literal stored in the String pool.
As a result:
status == "SUCCESS"may return:
falseand valid payments can fall into:
"Unknown payment status"7. Problems in the Bad Code
Incorrect Comparison Operation
The code uses:
==for String content comparison.
This compares references instead of values.
Production Bug Risk
The code may behave correctly with hard-coded test values but fail with runtime values.
External API Values Are Runtime Data
The status comes from JSON deserialization.
Business logic should compare the actual content.
Inconsistent Behavior
The same text may sometimes compare successfully and sometimes fail depending on how the String was created.
Difficult Debugging
Logs may show:
SUCCESSwhile the condition still evaluates to false.
Business Workflow Risk
A successful payment could be treated as unknown.
Possible consequences include:
- Order not confirmed
- Inventory not released correctly
- Retry logic triggered unnecessarily
- Customer receives incorrect error
- Support investigation required
Security Risk
The example itself is primarily a correctness issue.
However, the same mistake becomes security-sensitive when applied to role, permission, or authorization strings.
8. Code Review Findings
A senior Java reviewer should immediately notice:
Finding 1: == Used for String Value Comparison
status == "SUCCESS"This is the main defect.
Finding 2: Runtime Data Makes the Defect More Serious
The value originates from an external API, so reference identity must never be assumed.
Finding 3: Null Handling Should Be Considered
If:
response.getStatus()can return null, code such as:
status.equals("SUCCESS")would throw NullPointerException.
A null-safe comparison should be selected.
Finding 4: Repeated String Status Values May Need Stronger Typing
If payment statuses are fixed values such as:
SUCCESS
FAILED
PENDING
CANCELLEDan enum may provide a safer domain model.
Finding 5: Unknown External Status Should Be Handled Deliberately
External systems may introduce new status values.
The fallback behavior should be explicit rather than accidental.
9. Reviewer Comment Example
status is a String, so == compares object references rather than content. Please use equals() for the business-status comparison.Since the gateway status may be null, consider "SUCCESS".equals(status) to keep the comparison null-safe.These payment statuses appear to be a fixed domain set. Would mapping the gateway value to an enum make the workflow safer?Please add a test using new String("SUCCESS") or a deserialized value so this comparison bug cannot regress.The current unknown-status branch is useful, but the SUCCESS/FAILED checks need value comparison rather than reference comparison.
10. Improved Code
A straightforward improvement is:
import org.springframework.stereotype.Service;
@Service
public class PaymentStatusService {
public PaymentResult process(
PaymentGatewayResponse response) {
String status = response.getStatus();
if ("SUCCESS".equals(status)) {
return new PaymentResult(
true,
"Payment completed"
);
}
if ("FAILED".equals(status)) {
return new PaymentResult(
false,
"Payment failed"
);
}
return new PaymentResult(
false,
"Unknown payment status"
);
}
}If payment statuses form a stable set, a stronger design may be:
public enum PaymentStatus {
SUCCESS,
FAILED,
PENDING,
CANCELLED
}The gateway string can be converted at the integration boundary.
public PaymentStatus parseStatus(
String status) {
if (status == null) {
throw new IllegalArgumentException(
"Payment status must not be null"
);
}
try {
return PaymentStatus.valueOf(status);
} catch (IllegalArgumentException ex) {
throw new UnsupportedPaymentStatusException(
status
);
}
}Then business logic uses enum comparison:
if (status == PaymentStatus.SUCCESS) {
return new PaymentResult(
true,
"Payment completed"
);
}Using == with enums is valid because enum constants have identity semantics guaranteed by Java.
11. Improved Code Explanation
Content Comparison Is Used
The improved implementation uses:
"SUCCESS".equals(status)This compares String contents.
Null-Safe Comparison
If:
status == nullthen:
"SUCCESS".equals(status)returns:
falseinstead of throwing NullPointerException.
Runtime String Creation No Longer Matters
The result is correct whether the status came from:
- String literal
- Database
- JSON
- HTTP
- File
new String(...)
Domain Modeling Can Be Improved Further
Using an enum removes repeated raw String comparison from business code.
Unknown Values Are Explicit
External values that do not map to known statuses can be handled intentionally.
This is safer than allowing unknown Strings to spread throughout the application.
12. Bad Code vs Improved Code
| Area | Bad Code | Improved Code |
|---|---|---|
| Comparison | Reference comparison | Content comparison |
| Runtime values | Unreliable | Reliable |
| Null handling | Potential issue | Null-safe |
| Readability | Misleading | Intent is clear |
| Debugging | Inconsistent behavior | Predictable behavior |
| Testability | May pass accidentally | Easy to verify |
| Reliability | Business branch may fail | Correct value-based branching |
| Domain safety | Raw strings everywhere | Enum possible where justified |
13. Real Project Scenario
Consider a user-management microservice.
The database stores account states:
ACTIVE
SUSPENDED
LOCKEDA developer writes:
if (user.getStatus() == "ACTIVE") {
allowLogin();
}During unit testing, the test creates the user using:
new User("ACTIVE");The condition may appear to work depending on how the String is created.
In production, Hibernate loads:
ACTIVEfrom the database.
The String value has the same characters, but reference identity is not something the application should rely on.
The login condition may fail.
The user sees:
"Account is not active"even though the database clearly shows:
ACTIVEOperations teams inspect the database and see no data problem.
Developers inspect logs and also see:
status=ACTIVEThe bug remains confusing until someone notices the use of ==.
Correct implementation:
if ("ACTIVE".equals(user.getStatus())) {
allowLogin();
}Even better, if account status is a true domain state:
@Enumerated(EnumType.STRING)
private AccountStatus status;Then:
if (user.getStatus() == AccountStatus.ACTIVE) {
allowLogin();
}is correct because enum values should be compared with ==.
14. Production Impact
Incorrect String comparison can produce realistic production failures.
Incorrect Business Results
Status checks can fail even when values appear identical.
Workflow Failures
Examples:
- Successful payment treated as failed
- Active user treated as inactive
- Approved request treated as pending
- Completed job treated as incomplete
Retry Storms
If an external API returns:
SUCCESSbut the application fails to recognize it, retry logic may repeatedly call the external service.
Duplicate Processing
A completed operation may be processed again because its state comparison fails.
Customer-Facing Errors
Valid requests may be rejected.
Difficult Debugging
Logs show correct values, which makes the issue appear unrelated to comparison logic.
Security Consequences
If role or access-state comparisons are incorrect, authorization logic may behave unexpectedly.
The exact security impact depends on whether failure results in unintended permission or unintended denial.
15. Common Developer Mistakes
Mistake 1: Using == for Strings
if (status == "ACTIVE") {
}This compares references.
Mistake 2: Assuming Literals Prove == Works
String a = "JAVA";
String b = "JAVA";
System.out.println(a == b);This may print:
truebecause literals are pooled.
That does not make == correct for value comparison.
Mistake 3: Calling equals() on a Nullable Variable
status.equals("ACTIVE");If status is null:
NullPointerExceptionMistake 4: Using equalsIgnoreCase() Without Requirement
"admin".equalsIgnoreCase(role)may be wrong if roles are intentionally case-sensitive.
Mistake 5: Trimming Before Every Comparison
status.trim().equals("ACTIVE")This changes semantics.
If whitespace indicates invalid upstream data, silently trimming can hide a data-quality problem.
Mistake 6: Calling intern() to Make == Work
Avoid:
status.intern() == "ACTIVE"This is unnecessary and obscures intent.
Use:
"ACTIVE".equals(status)Mistake 7: Comparing Raw Strings for Fixed Domain States Everywhere
Repeated literals such as:
"ACTIVE"
"FAILED"
"PENDING"may indicate that an enum or value type is more appropriate.
Mistake 8: Ignoring Unicode and Locale Requirements
equals() compares exact Unicode character sequences.
Natural-language comparison may require different handling.
Mistake 9: Using contains() Instead of equals()
Avoid:
status.contains("SUCCESS")when exact equality is required.
Values such as:
"NOT_SUCCESSFUL"could produce incorrect results.
Mistake 10: Using compareTo() == 0 Without Need
This works for exact lexical equality:
first.compareTo(second) == 0but:
first.equals(second)is clearer when equality is the requirement.
16. Edge Cases
Null Values
Potentially unsafe:
status.equals("ACTIVE")Safe:
"ACTIVE".equals(status)If both variables may be null:
Objects.equals(first, second)Empty String
"".equals(value)checks whether the value is exactly empty.
This is different from:
value == nulland different from blank input.
Blank String
Java 11+ provides:
value.isBlank()for checking whitespace-only values.
Do not confuse blank checks with equality.
Case Differences
"ACTIVE".equals("active")returns:
falseUse case-insensitive comparison only if required.
Leading and Trailing Spaces
"ACTIVE".equals(" ACTIVE ")returns:
falseDecide whether spaces should be:
- Rejected
- Normalized
- Preserved
based on business requirements.
Unicode
Visually similar text may not always have identical Unicode representation.
For ordinary business status codes such as:
ACTIVEthis is usually not a concern.
For user-entered natural-language text, Unicode normalization requirements may need separate handling.
Database CHAR Columns
Fixed-width database columns or upstream systems may produce padded values.
Do not automatically hide this problem.
Verify the persistence or mapping behavior.
External API Status
External providers may add new values.
Do not assume only currently documented statuses will exist forever.
17. Performance Considerations
String equality is normally not a meaningful performance concern in typical Spring Boot business logic.
String.equals() compares:
- Object identity as an early optimization
- Length
- Character content when necessary
The worst-case work is proportional to the number of characters compared.
For typical status values such as:
SUCCESS
ACTIVE
FAILEDthe cost is negligible.
Avoid Premature Optimization
Do not replace:
"SUCCESS".equals(status)with reference comparison for performance.
Correctness is more important.
Large Strings
Comparing very large strings repeatedly can have measurable cost because equality may require scanning many characters.
Examples might include:
- Large document content
- Large payload hashes stored incorrectly as raw text
- Huge generated strings
In such cases, optimize the actual data model rather than using ==.
Repeated Normalization
This pattern:
input.trim()
.toLowerCase()
.equals("active")creates additional objects and may be unnecessary.
Normalize input once when appropriate.
Database Calls
String comparison itself does not increase database calls.
Any performance concern involving database access should be reviewed separately.
18. Security Considerations
String comparison can become security-sensitive depending on the data being compared.
Authorization Values
Incorrect:
if (role == "ADMIN") {
allowAdminAction();
}Correct content comparison:
if ("ADMIN".equals(role)) {
allowAdminAction();
}Better where appropriate:
if (role == Role.ADMIN) {
allowAdminAction();
}Authentication Tokens
General business String equality may not be suitable for comparing cryptographic secrets because timing behavior can matter.
For security-sensitive byte-level secret comparison, specialized constant-time comparison techniques may be required.
That is separate from ordinary String.equals() usage.
Case Sensitivity
Do not make security identifiers case-insensitive unless the security specification says they are case-insensitive.
Input Normalization
Do not normalize security-sensitive identifiers casually.
For example, changing case or trimming may change semantics.
Data Exposure
String comparison itself does not expose data.
However, avoid logging sensitive values merely to debug equality problems.
19. Testing Considerations
Tests should ensure comparisons work with dynamically created strings.
Positive Test
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;
class StringComparisonTest {
@Test
void shouldMatchStringsWithSameContent() {
String status =
new String("SUCCESS");
assertTrue(
"SUCCESS".equals(status)
);
}
}This test is useful because new String() guarantees a separate object.
Demonstrating the Incorrect Behavior
import static org.junit.jupiter.api.Assertions.assertFalse;
@Test
void shouldShowWhyReferenceComparisonIsWrong() {
String status =
new String("SUCCESS");
assertFalse(
status == "SUCCESS"
);
}Null Test
import static org.junit.jupiter.api.Assertions.assertFalse;
@Test
void shouldHandleNullStatusSafely() {
String status = null;
assertFalse(
"SUCCESS".equals(status)
);
}Case-Sensitivity Test
@Test
void shouldTreatDifferentCaseAsDifferentWhenRequired() {
assertFalse(
"SUCCESS".equals("success")
);
}Service Test
Test business behavior using a runtime-created status.
@Test
void shouldProcessSuccessfulGatewayResponse() {
PaymentGatewayResponse response =
new PaymentGatewayResponse();
response.setStatus(
new String("SUCCESS")
);
PaymentStatusService service =
new PaymentStatusService();
PaymentResult result =
service.process(response);
assertTrue(result.success());
}Integration Test
If status values come from:
- Database
- Kafka
- REST
- External provider
an integration test should verify mapping behavior when that integration is important.
20. Refactoring Guidelines
Step 1: Search for String Reference Comparisons
Look for:
== "VALUE"
"VALUE" == variable
!= "VALUE"Step 2: Confirm the Intent
Determine whether the code intends:
- Content equality
- Reference identity
For Strings, business code almost always intends content equality.
Step 3: Replace With equals()
Example:
status == "ACTIVE"becomes:
"ACTIVE".equals(status)Step 4: Review Null Semantics
Do not blindly replace with:
status.equals("ACTIVE")if status may be null.
Step 5: Check Case Requirements
Determine whether comparison should be:
equals()or:
equalsIgnoreCase()Do not change case semantics during refactoring.
Step 6: Check Whitespace Semantics
Do not introduce trimming unless required.
Step 7: Identify Repeated Domain Constants
If many classes compare:
"ACTIVE"
"SUSPENDED"
"LOCKED"consider migrating to:
AccountStatusStep 8: Add Tests
Test:
- Runtime-created Strings
- Null values
- Expected case behavior
- Unknown values
Step 9: Refactor Incrementally
Do not convert every String field to enums without reviewing:
- Database mapping
- JSON contracts
- Backward compatibility
- External API values
21. Best Practices
Use equals() for String Values
"SUCCESS".equals(status)Use Objects.equals() When Both Variables May Be Null
Objects.equals(first, second)This returns true when both are null.
Use Enums for Stable Closed Sets
Good candidates include:
- Order status
- Payment status
- Account state
- User role
- Processing stage
Keep External Strings at Boundaries
Convert external provider values into application-level types when practical.
Validate Unknown Values Explicitly
Do not silently treat every unknown status as failure unless that is the intended business rule.
Use Constants When Enum Is Not Appropriate
Example:
public static final String SUCCESS = "SUCCESS";This avoids repeated literals, although it does not provide enum-level type safety.
Keep Comparison Semantics Obvious
Code should make it clear whether comparison is:
- Exact
- Case-insensitive
- Normalized
- Partial
22. Practices to Avoid
Using == for String Content
Avoid:
status == "ACTIVE"Using != for String Content
Avoid:
status != "ACTIVE"Use:
!"ACTIVE".equals(status)Depending on String Pool Behavior
Do not write business code that only works because literals are interned.
Using intern() to Force Identity
Avoid unnecessary String-pool manipulation.
Applying equalsIgnoreCase() Everywhere
It may weaken validation or change business semantics.
Comparing After Hidden Normalization
Avoid:
value.trim()
.toLowerCase()
.equals(expected)unless normalization is explicitly required.
Using contains() for Exact Business States
Avoid approximate comparison when exact equality is required.
Repeating Magic Strings Throughout the Application
If the value is a stable business state, consider a stronger type.
23. Code Review Checklist
- Is any String being compared using
==? - Is any String being compared using
!=? - Does the code intend value equality or reference identity?
- Could the compared String come from a database?
- Could the value come from JSON or HTTP input?
- Could the value be null?
- Should the literal be placed on the left side of
equals()for null safety? - Would
Objects.equals()be clearer because both values may be null? - Is the comparison intentionally case-sensitive?
- Is
equalsIgnoreCase()actually required? - Is the code trimming or normalizing input without a business requirement?
- Are important business states represented by repeated raw strings?
- Would an enum be safer for these values?
- Are unknown external values handled explicitly?
- Could this comparison affect authorization or access control?
- Are tests using dynamically created Strings rather than only literals?
- Are external status values converted at the integration boundary?
- Is
contains()incorrectly being used where exact equality is required? - Is
compareTo() == 0being used whereequals()would communicate intent better? - Is the comparison behavior documented by tests?
24. Common Pull Request Review Comments
This is comparing String references with ==. Please use equals() because the status can be created dynamically at runtime.
status may be null here, so "ACTIVE".equals(status) would be safer than status.equals("ACTIVE").
This value comes from the database, so we should not depend on String-pool identity. Please compare the actual contents.
These status values are repeated in several services. Could we model them as an enum so invalid states cannot propagate as arbitrary strings?
equalsIgnoreCase() changes the accepted input semantics. Is case-insensitive matching actually required by the API contract?
Please avoid trimming before comparison unless whitespace is intentionally ignored. Otherwise we may hide malformed upstream data.
contains("SUCCESS") is too broad for a payment status check. This should use exact equality.
Can we add a test using new String("SUCCESS") so the test does not pass accidentally because both values are pooled literals?
This role comparison is part of authorization logic. Please replace the String reference comparison and consider using the Role enum.
No need to call intern() here. equals() directly expresses the comparison we need.
25. Code Review Exercise
Review the following Spring Boot order service.
Identify:
- Incorrect String comparisons
- Null risks
- Business-logic risks
- Security concerns
- Better domain modeling opportunities
- Required tests
import org.springframework.stereotype.Service;
@Service
public class OrderAccessService {
public boolean canCancel(Order order, User user) {
if (order.getStatus() == "DELIVERED") {
return false;
}
if (order.getStatus() == "CANCELLED") {
return false;
}
if (user.getRole().equals("ADMIN")) {
return true;
}
if (user.getRole() == "CUSTOMER" && order.getCustomerId().equals(user.getId())) {
return true;
}
return false;
}
}Review:
DELIVEREDCANCELLEDADMINCUSTOMER- Null behavior
- Role modeling
- Order status modeling
Do not assume all comparisons require the same refactoring.
26. Exercise Solution
Several issues exist.
Issue 1: Order Status Uses ==
order.getStatus() == "DELIVERED"This compares references.
The same problem exists for:
"CANCELLED"Issue 2: Customer Role Uses ==
user.getRole() == "CUSTOMER"This can fail for dynamically created role values.
Issue 3: Potential NullPointerException
user.getRole().equals("ADMIN")throws NullPointerException if:
user.getRole() == nullIssue 4: Security-Sensitive Role Comparison
The role determines authorization.
Comparison must be predictable.
Minimum Safe Refactoring
import org.springframework.stereotype.Service;
@Service
public class OrderAccessService {
public boolean canCancel(
Order order,
User user) {
if ("DELIVERED".equals(
order.getStatus()
)) {
return false;
}
if ("CANCELLED".equals(
order.getStatus()
)) {
return false;
}
if ("ADMIN".equals(
user.getRole()
)) {
return true;
}
return "CUSTOMER".equals(
user.getRole()
)
&& order.getCustomerId()
.equals(user.getId());
}
}This fixes reference comparison and null risk.
Stronger Domain Modeling
If statuses and roles are fixed business concepts:
public enum OrderStatus {
CREATED,
PAID,
SHIPPED,
DELIVERED,
CANCELLED
}
public enum Role {
ADMIN,
CUSTOMER
}Then the entity or domain model can use:
private OrderStatus status;and:
private Role role;The service becomes:
import org.springframework.stereotype.Service;
@Service
public class OrderAccessService {
public boolean canCancel(
Order order,
User user) {
if (order.getStatus()
== OrderStatus.DELIVERED) {
return false;
}
if (order.getStatus()
== OrderStatus.CANCELLED) {
return false;
}
if (user.getRole()
== Role.ADMIN) {
return true;
}
return user.getRole()
== Role.CUSTOMER
&& order.getCustomerId()
.equals(user.getId());
}
}Why == Is Correct Here
Enum constants have guaranteed identity.
Therefore this is correct:
user.getRole() == Role.ADMINThis is fundamentally different from comparing ordinary Strings using ==.
Testing
Tests should cover:
- Admin can cancel eligible order
- Customer can cancel own eligible order
- Customer cannot cancel another customer's order
- Delivered order cannot be cancelled
- Cancelled order cannot be cancelled
- Null role behavior
- Runtime-created String values if legacy String model remains
27. Interview Perspective
This is a very common Core Java interview and code-review topic.
An interviewer may ask:
"What is the difference between == and equals() for Strings?"
A complete answer should explain:
==compares references.equals()compares content.- String literals may be pooled.
- String-pool behavior can make incorrect code appear correct.
- Runtime-created Strings may have different references.
- Null handling matters.
- Enums are different because
==is appropriate for enum constants.
A code-review interview may show:
String status =
new String("ACTIVE");
if (status == "ACTIVE") {
System.out.println("Active");
}A strong candidate should immediately identify the reference-comparison bug.
For senior-level discussion, interviewers may go further:
- When should
Objects.equals()be used? - When should
equalsIgnoreCase()be used? - Should statuses remain Strings?
- When should enums replace strings?
- Why is
intern()not a normal solution? - What happens when strings come from databases or JSON?
- How would you test this bug?
28. Interview Questions and Answers
Basic Question
Question: What is the difference between == and equals() for Java Strings?
Answer:
== compares whether two references point to the same object.
equals() compares the character content of the strings.
For normal String value comparison, use equals().
Intermediate Question
Question: Why can == sometimes return true for two equal string literals?
Answer:
Java stores string literals in the String pool.
Two identical literals can reference the same pooled object.
Example:
String a = "JAVA";
String b = "JAVA";
a == bmay return true because both variables reference the same pooled String.
Business code should still use equals() for value comparison.
Advanced Question
Question: What is the safest way to compare a nullable status with a constant?
Answer:
Use:
"ACTIVE".equals(status)If status is null, the result is simply false.
Another option when both operands are variables is:
Objects.equals(first, second)Scenario-Based Question
Question: A payment API returns "SUCCESS", logs show "SUCCESS", but the code enters the failure branch. What would you inspect?
Answer:
Inspect whether the code uses:
status == "SUCCESS"The value may have been created by JSON deserialization and therefore have a different object reference.
Use:
"SUCCESS".equals(status)Code-Review Question
Question: What is wrong with this code?
if (user.getRole() == "ADMIN") {
allow();
}Answer:
It performs reference comparison on a String.
Role checks require deterministic value comparison.
Use:
"ADMIN".equals(user.getRole())or preferably use a Role enum if roles form a fixed domain set.
Real-Project Question
Question: Why might a String comparison bug pass unit tests but fail in production?
Answer:
Tests may use hard-coded literals:
"SUCCESS"which are pooled.
Production values may come from:
- Database
- REST
- JSON
- Files
- Runtime transformations
Those values can be separate String objects with identical content.
Reference comparison therefore behaves differently.
Enum Question
Question: Is == correct for Java enums?
Answer:
Yes.
Enum constants are single instances defined by the enum type.
Therefore:
status == PaymentStatus.SUCCESSis the normal and recommended comparison.
Null-Safety Question
Question: What is the difference between these two expressions?
status.equals("ACTIVE")and:
"ACTIVE".equals(status)Answer:
Both compare content when status is non-null.
If status is null, the first throws NullPointerException.
The second safely returns false.
29. Quick Rule to Remember
Use equals() for String values; use == only when you intentionally mean object identity, not matching text.
30. Final Takeaway
Correct String comparison is a basic Java rule with major production impact.
Developers should remember:
- Strings are objects.
==compares references.equals()compares contents.- String-pool behavior can make incorrect code appear correct.
- Database, HTTP, JSON, file, and runtime-created Strings must be compared by value.
"VALUE".equals(variable)is a simple null-safe pattern.Objects.equals()is useful when both variables may be null.equalsIgnoreCase()should only be used when the business rule is genuinely case-insensitive.intern()is not a replacement for proper comparison.- Exact state comparison should not use
contains(). - Stable domain states are often safer as enums.
==is correct for enum constants but not ordinary String content comparison.
During Pull Request review, treat String comparisons as correctness-sensitive code.
Whenever you see:
stringVariable == "VALUE"or:
stringVariable != "VALUE"verify the developer's intent immediately.
For business values, the comparison should almost always use content equality.
Production code should never depend on an accidental String-pool reference match to decide whether a payment succeeded, a customer is active, an order can be cancelled, or a user is authorized.