Zum Hauptinhalt springen
Echtzeit-Radar & Feeds
Alle RSS Feeds ➔
👥 Community & Social
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Windows Tipps & SecurityGrafikkarte vor Überhitzung schützen: So geht’s(25.09.2026 um 08:00 Uhr)
••••••••••
Intelligence View
⚡ tsecurity.de Intelligence

When “Clean Architecture” Isn’t So Clean: Rethinking Validation in the Domain

Some time ago, I was asked to maintain a Java application supposedly built around Clean Architecture. On paper, the domain layer was meant to be framework-agnostic and pure. In reality… not so much. Almost every entity fell into one of t…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Some time ago, I was asked to maintain a Java application supposedly built around Clean Architecture.

On paper, the domain layer was meant to be framework-agnostic and pure.



In reality… not so much.



Almost every entity fell into one of these categories:





  • Sprinkled with Jakarta annotations → framework leakage straight into the domain


  • Stuffed with Guava Preconditions.checkArgument() → intrusive utility dependencies


  • Polluted with ad-hoc null checks → inconsistent, repetitive, and rarely tested





The breaking point



I eventually stumbled upon a constructor like this (anonymized, but painfully real):




public Invoice(String customerEmail, BigDecimal amount, List<Item> items, LocalDate dueDate) {
this.customerEmail = Preconditions.checkNotNull(customerEmail, "email required");
Preconditions.checkArgument(customerEmail.contains("@"), "bad email");
Preconditions.checkArgument(customerEmail.length() < 100, "email too long");

this.amount = Preconditions.checkNotNull(amount);
Preconditions.checkArgument(amount.compareTo(BigDecimal.ZERO) > 0, "amount positive");

this.items = Preconditions.checkNotNull(items);
Preconditions.checkArgument(!items.isEmpty(), "items empty");
Preconditions.checkArgument(items.stream().allMatch(Objects::nonNull), "null item");

this.dueDate = Preconditions.checkNotNull(dueDate);
Preconditions.checkArgument(dueDate.isAfter(LocalDate.now()), "future date");
}






It worked, sure.

But it was noisy, brittle, and actively hostile to readability.



Worse: every failure collapsed into an IllegalArgumentException, making it impossible to distinguish bad user input from developer mistakes once it hit production.







What I tried first (and why it didn’t stick)



I initially experimented with the usual suspects:




  • Jakarta Bean Validation

  • Apache Commons Validate



But both came with trade-offs I couldn’t accept:




  • Strong coupling to external frameworks

  • Annotation-heavy APIs that don’t compose well for complex rules

  • Generic exceptions that flatten all validation failures into the same bucket



Bean Validation 3.0 + records improves things, but it’s still annotation-driven—and business rules rarely fit nicely into declarative annotations.







The constraints I set for myself



If I was going to introduce any dependency into the domain layer, it had to:




  1. Have zero transitive dependencies (single JAR, java.base only)

  2. Throw typed, meaningful exceptions (not IllegalArgumentException)

  3. Support fluent, readable chaining

  4. Allow custom predicates without turning everything into lambda soup







What I ended up building



The same constructor now looks like this:




public Invoice(String email, BigDecimal amount, List<Item> items, LocalDate dueDate) {
this.email = Assert.field("email", email)
.notBlank()
.email()
.maxLength(100)
.value();

this.amount = Assert.field("amount", amount)
.positive()
.value();

this.items = Assert.field("items", items)
.notEmpty()
.noNullElement()
.value();

this.dueDate = Assert.field("dueDate", dueDate)
.inFuture()
.value();
}






Same rules.

Far less noise.

And—most importantly—the intent reads like business logic.







Why this actually matters





1. Typed exceptions, not strings



Instead of this:




IllegalArgumentException: bad email






You now get something like:




EmailFormatInvalidException {
fieldName = "email",
invalidValue = "...",
constraint = "EMAIL_FORMAT"
}






This turned out to be huge.



Our monitoring can now automatically distinguish between:




  • User input errors → HTTP 400

  • Programming errors → HTTP 500



No fragile string matching. No guessing.









2. Composable, domain-level rules



Custom predicates are explicit and readable:




Assert.field("iban", iban)
.notBlank()
.satisfies(this::isValidIBANChecksum, "Checksum failed");






No annotations.

No reflection.

Just business rules, written where they belong.









The trade-offs (being honest)




  • Not for Hibernate-first teams

    If you’re all-in on Jakarta annotations and happy with @NotNull on JPA entities, this adds little value.


  • More verbose than annotations

    Yes, this is more code than @Email @NotNull.

    But it’s explicit, testable, debuggable—and doesn’t leak frameworks into your core.


  • No AOP magic

    You don’t slap this on method parameters and get auto-validation.

    This is intentional, constructor-level defense.










The bigger picture



After this experience, I’ve started to see validation libraries as falling into two camps:





  1. Framework validation (Jakarta, Spring)




  • Excellent at the HTTP/binding layer

  • Awful for domain purity





  1. Utility validation (Guava, Apache Commons)




  • Reusable

  • But weak on expressiveness and error semantics



There’s a missing middle ground:

domain-native validation that reads like business rules and fails like typed domain events.



That’s the gap I was trying to fill.









Discussion



For those doing DDD or Clean Architecture in Java:




  • How do you handle validation without contaminating your domain?

  • Do you accept Jakarta annotations in entities?

  • Do you write defensive code by hand?

  • Or have you found a different approach entirely?



And an honest question:

Do typed validation exceptions (e.g. NumberValueTooLowException) actually help you in production—or is this just over-engineering?



👉 Project: https://github.com/Sympol/pure-assert



Curious to hear how others approach this.

2. Cyber Threat Intelligence & Forensik

CTI Threat Relationship Graph2 Knoten / 1 Relationen
CVE / Incident Software MITRE ATT&CK CWE Weakness IoC
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten When “Clean Architecture” Isn’t So Clean: Rethinking Validation in the Domain

Thematisch verwandte Begriffe: When, Clean, Architecture, Isnt · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

💬 Kommentare werden geladen…
Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-101041 | The account recovery (password reset) functionality in the vulnerabilit…
Advisory →
tsecurity.de Icon
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag