Fundamentals 10 min read

How to Safeguard Your Code Against Unintended Modifications

The article explains how shared mutable state, such as global or static variables, can cause hard‑to‑debug bugs in multithreaded Java applications, and demonstrates that adopting immutability, pure functions, and event‑sourcing principles eliminates these issues, offering concrete coding guidelines and examples.

JavaEdge
JavaEdge
JavaEdge
How to Safeguard Your Code Against Unintended Modifications

Variable Hazards in Shared Mutable State

When a third‑party library stores mutable data in a static field, different threads can read and write the same object. A classic example is a static SimpleDateFormat that contains a Calendar field. The format() method modifies the calendar; if thread A formats a date, the calendar is set to A’s values, a context switch to thread B changes the calendar, and when A resumes it sees the altered state and produces an incorrect result.

Shared mutable SimpleDateFormat
Shared mutable SimpleDateFormat

A typical non‑thread‑safe pattern: counter = counter + 1; In a multithreaded environment the three steps are:

Thread A reads counter and computes a new value.

Thread B reads the same original value, computes its own new value and writes it.

Thread A writes its previously computed value, overwriting B’s update.

To avoid this, the mutable object must not be shared.

Declare the formatter as a local variable inside the method.

Or keep the mutable field inside the method scope and never expose it as static.

Immutability and Pure Functions

Functional programming separates two guarantees:

Values – data that is initialized once and never changed.

Pure functions – functions that return the same output for the same input and have no side effects.

When both are applied, code becomes referentially transparent and safe under concurrency because no shared mutable state exists.

Designing Immutable Classes

An immutable class in Java follows these rules:

All fields are private final and assigned only in the constructor.

No setter methods; any operation that would modify state returns a new instance.

All fields are themselves immutable or defensively copied.

Example (simplified):

public final class Money {
    private final BigDecimal amount;
    private final Currency currency;

    public Money(BigDecimal amount, Currency currency) {
        this.amount = amount;
        this.currency = currency;
    }

    public Money add(Money other) {
        return new Money(this.amount.add(other.amount), this.currency);
    }
}

Writing Pure Functions in Java

Guidelines:

Do not modify any field of the enclosing object.

Avoid calling methods that have side effects (e.g., I/O, mutable collections).

Prefer final parameters and local variables to signal that they will not be reassigned.

Rust illustrates the same principle: a binding without mut is immutable.

let result = 1; // immutable
let mut counter = 0; // mutable

Project Valhalla aims to introduce value types to Java, providing language‑level support for immutable data.

Event Sourcing as an Alternative to Mutable State

Instead of persisting the current mutable state, record each state‑changing operation as an immutable event. Replaying the event stream reconstructs the latest state, eliminating side effects caused by in‑place updates.

Practical Recommendations

Prefer local variables over static fields for objects that are not thread‑safe (e.g., SimpleDateFormat, Calendar).

When a shared instance is unavoidable, protect it with explicit synchronization ( synchronized block, ReentrantLock) or use thread‑local storage.

Mark variables that should not be reassigned with final. Use final on method parameters to document immutability intent.

Design domain objects as immutable value objects; use builders to assemble them and return new instances after modifications.

Adopt event‑sourcing patterns for aggregates where history is important; store events rather than mutable rows.

Key Takeaway

Restricting assignment statements and embracing immutability dramatically reduces concurrency bugs. By treating data as values and functions as pure, Java code can achieve the same reliability guarantees that functional languages provide.

Reference: https://www.youtube.com/watch?v=mGR0A5Jyolg
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javathread safetysoftware designfunctional programmingimmutability
JavaEdge
Written by

JavaEdge

First‑line development experience at multiple leading tech firms; now a software architect at a Shanghai state‑owned enterprise and founder of Programming Yanxuan. Nearly 300k followers online; expertise in distributed system design, AIGC application development, and quantitative finance investing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.