String Comparison Using equals()

21 min read

Core Java Correctness and Best Practices — review Java code for reference comparisons with == and replace them with correct, null-safe equals() usage.

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:

JAVA
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:

JAVA
paymentStatus.equals("SUCCESS");

or, when the variable may be null:

JAVA
"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
==

Java checks whether both variables reference the same object.

When strings are compared using:

JAVA
equals()

Java checks whether both strings contain the same sequence of characters.

Consider:

JAVA
String first = new String("ACTIVE");
String second = new String("ACTIVE");

The following comparison checks references:

JAVA
first == second

The result is:

JAVA
false

because two separate String objects were created.

However:

JAVA
first.equals(second)

returns:

JAVA
true

because both objects contain the same characters.

In production code, developers usually care about value equality rather than object identity.

For example:

JAVA
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:

JAVA
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:

JAVA
paymentStatus = SUCCESS

and:

JAVA
expectedStatus = SUCCESS

yet:

JAVA
paymentStatus == expectedStatus

returns 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.

JAVA
"ACTIVE".equals(status)

clearly means value comparison.

Using:

JAVA
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:

JAVA
String first = new String("ADMIN");
String second = new String("ADMIN");

System.out.println(first == second);

Output:

JAVA
false

== checks whether:

JAVA
first reference == second reference

It does not compare the characters.

Content Comparison with equals()

Example:

JAVA
System.out.println(first.equals(second));

Output:

JAVA
true

String.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:

JAVA
String first = "ACTIVE";
String second = "ACTIVE";

Both variables usually refer to the same pooled string object.

Therefore:

JAVA
first == second

may return:

JAVA
true

This is why developers sometimes incorrectly believe == is valid for Strings.

Now consider:

JAVA
String first = "ACTIVE";
String second = new String("ACTIVE");

Then:

JAVA
first == second

returns:

JAVA
false

while:

JAVA
first.equals(second)

returns:

JAVA
true

Production code should not rely on whether values happen to reference the same pooled String.

Case Sensitivity

equals() is case-sensitive.

JAVA
"ACTIVE".equals("active")

returns:

JAVA
false

If the business requirement explicitly says case should be ignored, use:

JAVA
"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) over status.equals("ACTIVE") when null is possible.
  • Use Objects.equals(first, second) when both values may be null.
  • 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 return true.
  • 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.

JAVA
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:

JAVA
{
    "status": "SUCCESS"
}

Jackson deserializes the JSON into:

JAVA
PaymentGatewayResponse

The value may contain the text:

JAVA
SUCCESS

but it is not guaranteed to be the exact same object as the "SUCCESS" literal stored in the String pool.

As a result:

JAVA
status == "SUCCESS"

may return:

JAVA
false

and valid payments can fall into:

JAVA
"Unknown payment status"

7. Problems in the Bad Code

Incorrect Comparison Operation

The code uses:

JAVA
==

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:

JAVA
SUCCESS

while 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

JAVA
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:

JAVA
response.getStatus()

can return null, code such as:

JAVA
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:

JAVA
SUCCESS
FAILED
PENDING
CANCELLED

an 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:

JAVA
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:

JAVA
public enum PaymentStatus {
    SUCCESS,
    FAILED,
    PENDING,
    CANCELLED
}

The gateway string can be converted at the integration boundary.

JAVA
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:

JAVA
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:

JAVA
"SUCCESS".equals(status)

This compares String contents.

Null-Safe Comparison

If:

JAVA
status == null

then:

JAVA
"SUCCESS".equals(status)

returns:

JAVA
false

instead 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

AreaBad CodeImproved Code
ComparisonReference comparisonContent comparison
Runtime valuesUnreliableReliable
Null handlingPotential issueNull-safe
ReadabilityMisleadingIntent is clear
DebuggingInconsistent behaviorPredictable behavior
TestabilityMay pass accidentallyEasy to verify
ReliabilityBusiness branch may failCorrect value-based branching
Domain safetyRaw strings everywhereEnum possible where justified

13. Real Project Scenario

Consider a user-management microservice.

The database stores account states:

JAVA
ACTIVE
SUSPENDED
LOCKED

A developer writes:

JAVA
if (user.getStatus() == "ACTIVE") {
    allowLogin();
}

During unit testing, the test creates the user using:

JAVA
new User("ACTIVE");

The condition may appear to work depending on how the String is created.

In production, Hibernate loads:

JAVA
ACTIVE

from 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:

JAVA
"Account is not active"

even though the database clearly shows:

JAVA
ACTIVE

Operations teams inspect the database and see no data problem.

Developers inspect logs and also see:

JAVA
status=ACTIVE

The bug remains confusing until someone notices the use of ==.

Correct implementation:

JAVA
if ("ACTIVE".equals(user.getStatus())) {
    allowLogin();
}

Even better, if account status is a true domain state:

JAVA
@Enumerated(EnumType.STRING)
private AccountStatus status;

Then:

JAVA
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:

JAVA
SUCCESS

but 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

JAVA
if (status == "ACTIVE") {
}

This compares references.

Mistake 2: Assuming Literals Prove == Works

JAVA
String a = "JAVA";
String b = "JAVA";

System.out.println(a == b);

This may print:

JAVA
true

because literals are pooled.

That does not make == correct for value comparison.

Mistake 3: Calling equals() on a Nullable Variable

JAVA
status.equals("ACTIVE");

If status is null:

JAVA
NullPointerException

Mistake 4: Using equalsIgnoreCase() Without Requirement

JAVA
"admin".equalsIgnoreCase(role)

may be wrong if roles are intentionally case-sensitive.

Mistake 5: Trimming Before Every Comparison

JAVA
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:

JAVA
status.intern() == "ACTIVE"

This is unnecessary and obscures intent.

Use:

JAVA
"ACTIVE".equals(status)

Mistake 7: Comparing Raw Strings for Fixed Domain States Everywhere

Repeated literals such as:

JAVA
"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:

JAVA
status.contains("SUCCESS")

when exact equality is required.

Values such as:

JAVA
"NOT_SUCCESSFUL"

could produce incorrect results.

Mistake 10: Using compareTo() == 0 Without Need

This works for exact lexical equality:

JAVA
first.compareTo(second) == 0

but:

JAVA
first.equals(second)

is clearer when equality is the requirement.

16. Edge Cases

Null Values

Potentially unsafe:

JAVA
status.equals("ACTIVE")

Safe:

JAVA
"ACTIVE".equals(status)

If both variables may be null:

JAVA
Objects.equals(first, second)

Empty String

JAVA
"".equals(value)

checks whether the value is exactly empty.

This is different from:

JAVA
value == null

and different from blank input.

Blank String

Java 11+ provides:

JAVA
value.isBlank()

for checking whitespace-only values.

Do not confuse blank checks with equality.

Case Differences

JAVA
"ACTIVE".equals("active")

returns:

JAVA
false

Use case-insensitive comparison only if required.

Leading and Trailing Spaces

JAVA
"ACTIVE".equals(" ACTIVE ")

returns:

JAVA
false

Decide 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:

JAVA
ACTIVE

this 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:

JAVA
SUCCESS
ACTIVE
FAILED

the cost is negligible.

Avoid Premature Optimization

Do not replace:

JAVA
"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:

JAVA
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:

JAVA
if (role == "ADMIN") {
    allowAdminAction();
}

Correct content comparison:

JAVA
if ("ADMIN".equals(role)) {
    allowAdminAction();
}

Better where appropriate:

JAVA
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

JAVA
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

JAVA
import static org.junit.jupiter.api.Assertions.assertFalse;

@Test
void shouldShowWhyReferenceComparisonIsWrong() {
    String status =
            new String("SUCCESS");

    assertFalse(
            status == "SUCCESS"
    );
}

Null Test

JAVA
import static org.junit.jupiter.api.Assertions.assertFalse;

@Test
void shouldHandleNullStatusSafely() {
    String status = null;

    assertFalse(
            "SUCCESS".equals(status)
    );
}

Case-Sensitivity Test

JAVA
@Test
void shouldTreatDifferentCaseAsDifferentWhenRequired() {
    assertFalse(
            "SUCCESS".equals("success")
    );
}

Service Test

Test business behavior using a runtime-created status.

JAVA
@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:

JAVA
== "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:

JAVA
status == "ACTIVE"

becomes:

JAVA
"ACTIVE".equals(status)

Step 4: Review Null Semantics

Do not blindly replace with:

JAVA
status.equals("ACTIVE")

if status may be null.

Step 5: Check Case Requirements

Determine whether comparison should be:

JAVA
equals()

or:

JAVA
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:

JAVA
"ACTIVE"
"SUSPENDED"
"LOCKED"

consider migrating to:

JAVA
AccountStatus

Step 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

JAVA
"SUCCESS".equals(status)

Use Objects.equals() When Both Variables May Be Null

JAVA
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:

JAVA
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:

JAVA
status == "ACTIVE"

Using != for String Content

Avoid:

JAVA
status != "ACTIVE"

Use:

JAVA
!"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:

JAVA
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() == 0 being used where equals() would communicate intent better?
  • Is the comparison behavior documented by tests?

24. Common Pull Request Review Comments

  1. This is comparing String references with ==. Please use equals() because the status can be created dynamically at runtime.
  1. status may be null here, so "ACTIVE".equals(status) would be safer than status.equals("ACTIVE").
  1. This value comes from the database, so we should not depend on String-pool identity. Please compare the actual contents.
  1. These status values are repeated in several services. Could we model them as an enum so invalid states cannot propagate as arbitrary strings?
  1. equalsIgnoreCase() changes the accepted input semantics. Is case-insensitive matching actually required by the API contract?
  1. Please avoid trimming before comparison unless whitespace is intentionally ignored. Otherwise we may hide malformed upstream data.
  1. contains("SUCCESS") is too broad for a payment status check. This should use exact equality.
  1. Can we add a test using new String("SUCCESS") so the test does not pass accidentally because both values are pooled literals?
  1. This role comparison is part of authorization logic. Please replace the String reference comparison and consider using the Role enum.
  1. 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
JAVA
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:

  • DELIVERED
  • CANCELLED
  • ADMIN
  • CUSTOMER
  • 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 ==

JAVA
order.getStatus() == "DELIVERED"

This compares references.

The same problem exists for:

JAVA
"CANCELLED"

Issue 2: Customer Role Uses ==

JAVA
user.getRole() == "CUSTOMER"

This can fail for dynamically created role values.

Issue 3: Potential NullPointerException

JAVA
user.getRole().equals("ADMIN")

throws NullPointerException if:

JAVA
user.getRole() == null

Issue 4: Security-Sensitive Role Comparison

The role determines authorization.

Comparison must be predictable.

Minimum Safe Refactoring

JAVA
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:

JAVA
public enum OrderStatus {
    CREATED,
    PAID,
    SHIPPED,
    DELIVERED,
    CANCELLED
}

public enum Role {
    ADMIN,
    CUSTOMER
}

Then the entity or domain model can use:

JAVA
private OrderStatus status;

and:

JAVA
private Role role;

The service becomes:

JAVA
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:

JAVA
user.getRole() == Role.ADMIN

This 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:

JAVA
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:

JAVA
String a = "JAVA";
String b = "JAVA";

a == b

may 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:

JAVA
"ACTIVE".equals(status)

If status is null, the result is simply false.

Another option when both operands are variables is:

JAVA
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:

JAVA
status == "SUCCESS"

The value may have been created by JSON deserialization and therefore have a different object reference.

Use:

JAVA
"SUCCESS".equals(status)

Code-Review Question

Question: What is wrong with this code?

JAVA
if (user.getRole() == "ADMIN") {
    allow();
}

Answer:

It performs reference comparison on a String.

Role checks require deterministic value comparison.

Use:

JAVA
"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:

JAVA
"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:

JAVA
status == PaymentStatus.SUCCESS

is the normal and recommended comparison.

Null-Safety Question

Question: What is the difference between these two expressions?

JAVA
status.equals("ACTIVE")

and:

JAVA
"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:

JAVA
stringVariable == "VALUE"

or:

JAVA
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.