Neglecting Spec and Data Validation

Why unchecked inputs make Clojure systems brittle, and how specs or other validation boundaries prevent invalid data from leaking inward.

Spec: In Clojure, a declarative description of the shape or predicate constraints of data that can be used for validation, explanation, instrumentation, and test-data generation.

Neglecting spec and data validation is a Clojure anti-pattern because dynamic systems stay robust only when data boundaries are made explicit. If untrusted or loosely shaped data flows deep into the application before anyone checks it, the eventual failure becomes harder to diagnose and easier to mis-handle.

This is not an argument that every local value needs a spec. It is an argument that important boundaries need a contract.

Anti-Pattern: Trusting External Input Too Long

One of the most common failures is letting data from the outside world travel too far before validation:

  • HTTP request bodies
  • CLI arguments
  • environment config
  • queue payloads
  • database rows with weaker guarantees than the code assumes

Weak:

1(defn create-user! [req]
2  (persist-user! (:body req)))

If :body is malformed, the failure may appear much later in persistence, business rules, or rendering code.

The anti-pattern is not “missing spec syntax.” It is missing a validation boundary near the point where the system stops controlling the shape of the data.

Anti-Pattern: Treating Validation as a Late Error Message Problem

Sometimes teams validate only after doing real work with the data.

That leads to:

  • more confusing failures
  • partial side effects before rejection
  • weaker error messages
  • business logic polluted with defensive checks everywhere

A better pattern is boundary validation first, then ordinary domain logic on known-good data.

Anti-Pattern: Using Validation Only for Happy-Path Documentation

Specs are useful because they describe and validate data. They are weaker when they exist only as comments-in-code and the runtime path never actually checks them where it matters.

For example, defining:

1(s/def ::email string?)

does not help much if the boundary never validates incoming email values before downstream functions assume more than “is a string.”

The anti-pattern is believing the presence of a spec definition alone has made the system safer.

Anti-Pattern: Overvalidating Every Internal Step

The opposite mistake is also real: validating every tiny intermediate value just because specs exist.

That can create:

  • noisy code
  • performance overhead in the wrong places
  • a false sense that internal design problems are solved by repeated checks

A better rule is:

  • validate aggressively at trust boundaries
  • validate strategically at critical internal boundaries
  • do not turn every local transformation into ceremonial spec plumbing

The anti-pattern is not “too much safety” in the abstract. It is validation placed without regard for ownership and cost.

Anti-Pattern: Throwing Away Explainability

One of the best things about spec is not just s/valid?. It is the ability to explain why data failed the contract.

Weak:

1(if (s/valid? ::user payload)
2  ...
3  (throw (ex-info "invalid user" {})))

Stronger:

1(when-not (s/valid? ::user payload)
2  (throw
3    (ex-info "Invalid user payload"
4             {:type :user/invalid
5              :problems (s/explain-data ::user payload)})))

The anti-pattern is validating and then discarding the details that would make debugging and user feedback practical.

Anti-Pattern: Confusing Shape Validation with Business Validation

Specs and predicate-based validation are excellent for structure and low-level constraints:

  • required keys
  • types and predicates
  • allowed enum-like values
  • shape of nested maps

They do not automatically replace all domain rules.

Example:

  • :start-date is present and parseable” is validation
  • :start-date must be before :end-date unless this workflow is draft” is business logic

The anti-pattern is assuming one validation layer replaces all higher-level policy reasoning.

Anti-Pattern: No Boundary Contract for Functions with Rich Inputs

Some functions are effectively mini-APIs:

  • orchestration entry points
  • public library functions
  • namespace-level service boundaries

If those functions accept rich maps and never declare or validate the expected shape, callers guess. That guesswork tends to surface later as nil-punning, ad hoc key checks, and brittle implicit contracts.

Spec is one strong option here, especially when you want:

  • a reusable shape definition
  • explainable failure data
  • optional instrumentation or generative testing

The deeper point is not tool loyalty. It is explicit contract ownership.

A Better Validation Model

    flowchart TD
	    A["Untrusted or loosely shaped input"] --> B["Validate at boundary"]
	    B --> C{"Valid?"}
	    C -->|No| D["Return explainable failure"]
	    C -->|Yes| E["Convert to trusted domain data"]
	    E --> F["Run domain logic with fewer defensive checks"]

This keeps the rest of the system cleaner because the shape uncertainty is handled where it enters.

What to Do Instead

  • validate at trust boundaries first
  • keep validation close to parsing or ingress, not buried deep in domain code
  • use s/explain-data or equivalent detail when failures must be diagnosable
  • separate structural validation from richer business rules
  • use specs or other validation tools to clarify contracts for important public functions

Common Mistakes

  • letting external data travel far before validation
  • defining specs without actually enforcing them at important boundaries
  • validating every internal value indiscriminately
  • throwing away explainability after a failed check
  • assuming structural specs replace business-policy logic

Key Takeaways

  • Dynamic systems stay robust when data boundaries are explicit.
  • Validation belongs near the point where trust ends.
  • Specs are most valuable when they improve contracts, explainability, and testability.
  • Not all validation is the same; shape checks and business rules play different roles.
  • The real anti-pattern is vague data contracts, not merely the absence of one particular library.

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026