Class NoBlanketCatch

java.lang.Object
com.aim2be.platform.archunit.NoBlanketCatch

public final class NoBlanketCatch extends Object
ArchUnit rule enforcing ADR-0013 §3: no blanket 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 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 — see NoBlanketCatchArchTest
           @ArchTest
           static final ArchRule rule = NoBlanketCatch.noBlanketCatchOutsideBoundary();
       }
       

      importOptions = ImportOption.DoNotIncludeTests.class is 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 named NoBlanketCatchGateTest (NOT NoBlanketCatchArchTest) so it never shadows this module's consumer-facing helper of that name.

      Returns:
      the ArchRule (reusable across test classes)