Skip to content

java: the '@Component JavaDelegate' rule is a publish-time Problems entry, not a per-execution WARN - #7316

Merged
delchev merged 1 commit into
masterfrom
fix/7291-component-javadelegate
Sep 11, 2026
Merged

java: the '@Component JavaDelegate' rule is a publish-time Problems entry, not a per-execution WARN#7316
delchev merged 1 commit into
masterfrom
fix/7291-component-javadelegate

Conversation

@ThuF

@ThuF ThuF commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Moves the rule "a JavaDelegate must NOT be a @Component" from a per-execution log line to a publish-time Problems-view entry, and makes the log line that remains state itself once instead of forever.

Since #7272 the rule was observable only as a WARN in ComponentContainer.createUnmanaged. Two problems with that placement:

  1. It fired on every execution, forever. On the ${JavaTask} + handler path, DirigibleJavaCallDelegate.instantiate calls createUnmanaged for each execute — the delegate is fresh per execution by design. An annotated handler on a step that runs a thousand times a day logged a thousand identical WARNs.
  2. It never reached the developer who wrote the annotation. A delegate nobody has executed yet warned nothing, and an operator reading a WARN in the Logs view is not the person who added @Component.

Changes:

  • Publish-time check. ComponentContainer.rebuild now flags a bean implementing Flowable's JavaDelegate as a wiringWarnings() entry, naming the class and the rule. The interface is matched by name (org.flowable.engine.delegate.JavaDelegate), walking superclasses and super-interfaces, because engine-java cannot see the Flowable type — the dependency runs the other way, which is also why the execution-time check is the broader isBean.
  • It is a warning, not a wiring error. wiringWarnings() is a new map beside wiringErrors(), carried on RebuildResult and projected by JavaSynchronizer through the same recordCompilationProblems path. The artefact stays CREATED: the bean is built and usable, so only the Problems entry appears. Routing it through wiringErrors() would have marked the file FAILED, which would be a lie about a class that works.
  • Once-then-DEBUG at execution time. The WARN is kept — a delegate can arrive from an AOT-compiled module the synchronizer never saw — but is logged once per class per generation and then at DEBUG, the shape fix(java): retry a client-Java listener subscription that failed at startup (#7217) #7220/java: a refused listener subscription closes the connection it started (#7264) #7267 established for exactly this class of "the same fact, every tick" logging. The suppression set is cleared by rebuild, so a republish states it again for the developer who just changed the class. The DEBUG branch is isDebugEnabled-guarded so a suppressed repeat does not even build its message on a per-execution path.
  • The message no longer over-claims. It said the class "is a JavaDelegate annotated @component" while the check was isBean on whatever class was handed to createUnmanaged; it now says "is annotated @Component but is instantiated outside the container".

What issues does this PR fix or reference?

Fixes #7291. Follow-up to #7223 / #7272; reuses the once-then-DEBUG pattern from #7220 and #7267.

Notes for reviewers

  • RebuildResult gained a component. Its only construction site is JavaLoader.rebuild, and every consumer of the record lives inside engine-java, so no downstream module is affected.
  • The wiringWarnings branch sits inside the succeededFqns arm of JavaSynchronizer.rebuildAll, so it records the Problems entry where the code previously cleared them — a class that failed to compile or failed to wire still reports that stronger failure instead.
  • Test fixture in org.flowable.*. src/test/java/org/flowable/engine/delegate/JavaDelegate.java is a name-only stand-in (no execute method — the name is the whole of what the container checks). It is what makes the by-name match testable at all, given engine-java has no Flowable dependency.
  • Coverage: new ComponentContainerDelegateRuleTest (8 tests) — the warning's content, that it does not fail the bean it is about, a delegate found through a super-interface, a non-delegate bean warning nothing, the previous generation's warning being cleared, exactly one WARN and two DEBUGs across three executions, the reworded execution-time message, and re-warning after a republish. Full module suite: 145 tests, 0 failures.
  • license-maven-plugin:check cannot run from a git worktree at all (Bare Repository has neither a working tree, nor an index), so local verification used -Dlicense.skip=true; every new file carries the standard EPL header, and the formatter validates clean.
  • engine-java/CLAUDE.md is updated with where the rule is checked, why a warning rather than an error, and the guidance behind that choice.

Release Notes

N/A — bug fix.

Documentation

N/A — no user-facing documentation change; the developer-facing note lives in components/engine/engine-java/CLAUDE.md.

🤖 Generated with Claude Code

…ntry (#7291)

The rule "a JavaDelegate must NOT be a @component" was observable only as a
WARN from ComponentContainer.createUnmanaged. On the ${JavaTask} + handler
path that method runs for every execution of the step (the delegate is fresh
per execution by design), so an annotated handler on a step that runs a
thousand times a day logged a thousand identical WARNs - and none of them
reached the developer who wrote the annotation, only whoever happened to read
the log of a run.

- ComponentContainer.rebuild now flags a bean that implements Flowable's
  JavaDelegate - matched by interface NAME, since engine-java cannot see the
  Flowable type - as a wiringWarnings() entry, carried on RebuildResult and
  projected by JavaSynchronizer onto the Problems view at publish. It is a
  warning, not a wiring error: the bean is built and usable, so the artefact
  stays CREATED and only the Problems entry appears.
- The execution-time WARN is kept (a delegate can arrive from an AOT module
  the synchronizer never saw) but is logged once per class per generation and
  then at DEBUG, the once-then-DEBUG shape #7220/#7267 established. The
  suppression set is cleared by rebuild, so a republish states it again.
- Its message no longer claims the class "is a JavaDelegate": the check there
  is isBean on whatever class was asked to be wired unmanaged, so it now says
  "instantiated outside the container" instead.

Coverage: ComponentContainerDelegateRuleTest, with a name-only
org.flowable.engine.delegate.JavaDelegate stand-in under src/test/java - which
is what makes the by-name match testable without the dependency.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@delchev
delchev merged commit cb767ee into master Sep 11, 2026
10 checks passed
@delchev
delchev deleted the fix/7291-component-javadelegate branch September 11, 2026 06:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants