Class NoBlanketCatch
catch (Exception) /
catch (Throwable) / catch (RuntimeException) outside the
top-level request boundary.
Domain failures must be typed (each service's XxxError hierarchy)
so the structured error.event span event + <svc>_errors_total
counter carry a stable error code instead of a swallowed generic. A blanket
catch of Exception/Throwable/RuntimeException erases
that signal, so it is permitted ONLY at the top-level boundary — a
@RestControllerAdvice / @ControllerAdvice handler — where the
alternative is an uncaught request termination (ADR-0013 §3, Rule 02).
RuntimeException is included deliberately: the per-service
XxxError domain types are unchecked (RuntimeException
subclasses), so catch (RuntimeException) swallows the typed signal
exactly as catch (Exception) does — migrating one to the other would
satisfy the rule letter while defeating its purpose. The three generic
super-throwables are flagged; any narrower catch (a specific exception or a
typed XxxError) is allowed.
How the catch-clause type is inspected
ArchUnit's high-level DSL models class/member structure, not method
bodies, so catch-clause types aren't expressible in the fluent API.
This rule uses JavaCodeUnit.getTryCatchBlocks() (ArchUnit ≥ 1.2) and
inspects TryCatchBlock.getCaughtThrowables() directly — see
notCatchGenericThrowables().
Boundary exclusion
The two boundary annotations are matched by fully-qualified NAME so this
module needs no compile/test dependency on spring-web. The match is
direct OR meta (JavaClass.isAnnotatedWith(String) ‖
JavaClass.isMetaAnnotatedWith(String)): a service that introduces a
composed boundary annotation (e.g. @GlobalExceptionHandler
meta-annotated with @RestControllerAdvice) is still recognised as a
boundary, so the rule does not raise false positives against it. Because
@RestControllerAdvice is itself meta-annotated with
@ControllerAdvice, a single direct-or-meta check on each FQN covers
both Spring annotations and any annotation composed from them.
Warn vs block
Per the L0-T0 #7 sweep decision the rule starts in WARN mode: a consuming
service evaluates it and logs the FailureReport WITHOUT failing the
build, then flips to a hard @ArchTest once the existing blanket
catches are typed. See NoBlanketCatchArchTest for both wirings.
- See Also:
-
Method Summary
Modifier and TypeMethodDescriptionstatic com.tngtech.archunit.lang.ArchRuleBuilds the rule.
-
Method Details
-
noBlanketCatchOutsideBoundary
public static com.tngtech.archunit.lang.ArchRule noBlanketCatchOutsideBoundary()Builds the rule. Consumers evaluate it against their own classpath via the standard ArchUnit API:@AnalyzeClasses(packagesOf = ApplicationMainClass.class, importOptions = ImportOption.DoNotIncludeTests.class) class NoBlanketCatchGateTest { // name it distinctly — seeNoBlanketCatchArchTest@ArchTest static final ArchRule rule = NoBlanketCatch.noBlanketCatchOutsideBoundary(); }importOptions = ImportOption.DoNotIncludeTests.classis required so the rule scans only production code — without it, test-scoped fixtures (which legitimately carry blanket catches) are flagged as false positives. The class is namedNoBlanketCatchGateTest(NOTNoBlanketCatchArchTest) so it never shadows this module's consumer-facing helper of that name.- Returns:
- the ArchRule (reusable across test classes)
-