Skip to content

fix(error): guard org.springframework.dao instanceof checks behind a classpath flag#14

Open
casc84ab wants to merge 1 commit into
mainfrom
fix/exception-metadata-aspect-springdao-optional
Open

fix(error): guard org.springframework.dao instanceof checks behind a classpath flag#14
casc84ab wants to merge 1 commit into
mainfrom
fix/exception-metadata-aspect-springdao-optional

Conversation

@casc84ab

Copy link
Copy Markdown
Contributor

Problem

ExceptionMetadataAspect (@Aspect @Component @Order(1), always registered) runs for every exception via @Around on ExceptionConverterService.convertException. It used hard checks:

if (exception instanceof org.springframework.dao.DataIntegrityViolationException) { ... }
else if (exception instanceof org.springframework.dao.QueryTimeoutException) { ... }

Those classes ship with spring-tx (pulled transitively by spring-data). fireflyframework-web declares spring-data-commons as optional, so data-less services (e.g. reactive BFFs without a DB) run without spring-tx on the classpath. There, the instanceof forces class resolution at runtime and throws NoClassDefFoundError: org/springframework/dao/DataIntegrityViolationException while building exception metadata — breaking the reactive error path: downstream errors surfaced as hangs/timeouts instead of clean 5xx responses.

This was latent and got exposed when services migrated off the old IdP stack (which dragged spring-tx in transitively) to the new fireflyframework-security-* modules.

Fix

Gate both checks behind a static flag and rely on && short-circuit:

private static final boolean SPRING_DAO_PRESENT = ClassUtils.isPresent(
        "org.springframework.dao.DataIntegrityViolationException",
        ExceptionMetadataAspect.class.getClassLoader());
...
if (SPRING_DAO_PRESENT && exception instanceof org.springframework.dao.DataIntegrityViolationException) { ... }
else if (SPRING_DAO_PRESENT && exception instanceof org.springframework.dao.QueryTimeoutException) { ... }
  • Absent (BFF / data-less): the && short-circuits, the instanceof (and its class resolution) is never reached → no NoClassDefFoundError.
  • Present (services with a DB): the instanceof runs unchanged, still matching the full subclass hierarchy (e.g. DuplicateKeyException) → existing behavior fully preserved.

This aligns the always-on aspect with the converters that already use @ConditionalOnClass for the same spring-dao types.

Verification

  • Compiles; bytecode confirms getstatic SPRING_DAO_PRESENT short-circuits before the instanceof.
  • Runtime: a reactive BFF (no spring-tx) calling a downed downstream now returns HTTP 503 in ~0.26s with zero NoClassDefFoundError (previously a 20s hang / empty reply).
  • Core services (with spring-tx) keep identical data-integrity / query-timeout metadata, subclasses included.

…classpath flag

ExceptionMetadataAspect runs for every exception and used hard
'instanceof org.springframework.dao.DataIntegrityViolationException/QueryTimeoutException'
checks. Those classes ship with spring-tx (pulled transitively by spring-data), which
fireflyframework-web declares as optional, so data-less services (e.g. reactive BFFs)
can run without it. There, the instanceof triggered a NoClassDefFoundError while building
exception metadata, breaking the reactive error path (downstream errors surfaced as
hangs/timeouts instead of clean 5xx).

Gate both checks with a static SPRING_DAO_PRESENT flag (ClassUtils.isPresent). The &&
short-circuits the instanceof when the classes are absent, avoiding class resolution.
When present (services with a database) the instanceof runs unchanged, still matching the
full subclass hierarchy (e.g. DuplicateKeyException), so existing behavior is preserved.

The converters that reference these types are already @ConditionalOnClass; this aligns the
always-on aspect with that defensive pattern.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant