This chapter shows how everyday coding choices can strengthen security without adding friction or time. By favoring the right constructs, developers can make secure behavior the default and reduce whole classes of vulnerabilities. The material focuses on three complementary strategies—immutability, fail-fast contracts, and layered validation—that together improve integrity, availability, and clarity of responsibility across a codebase.
First, immutability reduces contention and prevents illegal state changes by design. The chapter contrasts a mutable, synchronized customer model that suffered from hidden blocking and data tampering risks with an immutable redesign that eliminated locks, avoided reference leaks, and made sharing across threads safe. Second, it advocates failing fast through explicit contracts: state the preconditions callers must satisfy, the postconditions methods guarantee, and the invariants constructors must uphold. Enforcing these rules at boundaries and before operations—using clear exceptions and concise guard checks—stops bad data early, assigns responsibility unambiguously, and keeps objects from ever entering invalid states.
Finally, it distinguishes multiple kinds of validation and recommends a protective order: verify origin (who sent the data), limit size (to blunt asymmetric DoS), check lexical content (allowed characters and encodings), confirm syntax (format and checksums), and only then validate semantics against the domain model. This staging shields expensive parsers and business logic, prevents amplification tricks such as entity expansion, and localizes rules in domain-focused value objects so validated data doesn’t need to be rechecked. Combined, these practices make systems more resilient while keeping code simple, intention-revealing, and secure by design.
Summary
Data integrity is about assuring the consistency and accuracy of data during its entire lifecycle.
Data availability is about ensuring data is obtainable and accessible at the expected level of performance in a system.
Immutable values are safe to share between threads without locks: no locking, no blocking.
Immutability solves data availability issues by allowing scalability and no locking between threads.
Immutability solves data integrity issues by preventing change.
Contracts are an effective way to clarify the responsibilities of objects and methods.
It’s better to fail fast in a controlled manner than to risk uncontrolled failures later. Fail fast by checking preconditions early in each method.
Validation can be broken down into checking origin, data size, lexical content, syntactic format, and semantics.
Origin checks can be done by checking the origin IP or requiring an access key to counteract DDoS attacks.
Data size checks can be done both at the system border and at object creation.
Lexical content checks can be done with a simple regular expression (regexp).
Syntax format checks might require a parser, which is more expensive in terms of CPU and memory.
Semantic checks often require looking at the data in the database, such as searching for an entity with a specific ID.
Earlier steps in the validation order are more economical to perform and protect the later, more expensive steps. If early checks fail, later steps can be skipped.
FAQ
How does immutability promote security?Immutability prevents in-place state changes, which:
- Eliminates many integrity bugs (no accidental or illegal updates).
- Allows safe sharing across threads without locks, reducing contention and DoS risk.
- Simplifies reasoning and removes whole classes of concurrency errors (no partial initialization, no “when is it safe to read?” questions).
- Makes reference leaks far less dangerous since leaked objects can’t be mutated.What went wrong with the mutable Customer/CreditScore design?Several issues combined:
- Setters allowed re-initialization at any time (no “at-most-once” guarantees).
- Every method was synchronized, creating a single implicit lock and heavy thread contention.
- Returning internal mutable state leaked references, enabling external, unsynchronized mutation.
- CreditScore could be reassigned or even point to a different customer ID.
- A shared CreditScore instance could be injected into multiple customers, corrupting integrity.If objects are immutable, how do I handle updates?Separate reads from writes. Typical approaches:
- Create a new instance with updated values (copy-with).
- Use a snapshot pattern or event sourcing so writes go through dedicated channels.
- Recompute derived values externally and inject results via constructors.
- Store identity outside immutable value objects (aggregates hold references).What does “fail fast using contracts” mean in practice?Define and enforce method/class contracts up front:
- Check preconditions at the start and throw immediately on violation.
- Use appropriate exceptions: NullPointerException, IllegalArgumentException, IllegalStateException.
- Benefits: limits blast radius, simplifies debugging, and provides defense-in-depth by stopping bad data early.Preconditions, postconditions, invariants—what’s the difference?
- Preconditions: Must be true before a call (for example, argument not null; queue not empty).
- Postconditions: Must be true after a call (for example, size increased by 1).
- Invariants: Must always hold for an object (for example, fields never null; a name conforms to rules). Enforce invariants in constructors.How can I implement contract checks concisely in Java?Use Apache Commons Lang’s Validate helpers:
- notNull, isTrue, inclusiveBetween, matchesPattern, validState.
- Note: matchesPattern doesn’t return the validated value; most others do.
- Avoid echoing untrusted input in exception messages.
- Prioritize checks on public APIs; internal/private methods as needed by context.In what order should I validate input, and why?Recommended order:
1) Origin (legitimate sender?)
2) Size (reasonable total and per-field limits)
3) Lexical content (allowed characters/encoding)
4) Syntax (proper format/structure, checksums)
5) Semantics (makes sense in your domain)
Each stage is cheap-to-expensive; earlier checks shield later, costlier ones and mitigate DoS.How should I check data origin effectively?
- Internal calls: network-level allowlists (IP ranges, security groups).
- Public APIs: access keys/tokens with signatures (for example, Authorization header), ideally via an API gateway.
- Add rate limiting/blacklisting where appropriate.
- Validate key/token format and length to prevent new attack vectors.Why add length checks if I already use regexes or parsers?Length checks protect regex engines and parsers from excessive input and catastrophic work. They mitigate DoS (for example, massive strings, XML entity expansion). Enforce both total message size (for example, Content-Length) and per-field limits before regex or parse steps.Where should semantic validation live, and how can code reflect it?Place semantic rules in the domain model/services (for example, disallow changing shipped orders). Throw IllegalStateException on invalid operations. Encode validated values as domain primitives (for example, ISBN), so types document validation level and prevent repeated checks across the system.
pro $24.99 per month
access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!