Skip to content

bpm: a flowable:class execution or task listener reaches the client bean container too (#7222) - #7335

Merged
delchev merged 1 commit into
masterfrom
issue-7222-listener-bean-injection
Sep 12, 2026
Merged

bpm: a flowable:class execution or task listener reaches the client bean container too (#7222)#7335
delchev merged 1 commit into
masterfrom
issue-7222-listener-bean-injection

Conversation

@delchev

@delchev delchev commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Cause

#7189 (#7058) wires a client JavaDelegate through the bean container on both service-task paths, but BpmFlowableConfig registered only the activity-behaviour factory. Flowable creates a listener through a different one: ProcessEngineConfigurationImpl.initListenerFactory builds its own DefaultListenerFactory carrying a stock DefaultClassDelegateFactory, and createClassDelegateExecutionListener / createClassDelegateTaskListener call that.

So a <flowable:executionListener class="..."> or <flowable:taskListener class="..."> written the way the new docs teach — a constructor collaborator, or an @Inject field — either failed at reflection (no no-arg constructor) or ran with every injected field null, silently. That is the #7058 symptom one artefact type over, and it made ResilientClassDelegateFactory.create(String, List) — the overload the listener path uses — dead code under this configuration.

Change

BpmFlowableConfig additionally registers setListenerFactory(new DefaultListenerFactory(classDelegateFactory)) with the same factory instance, so both listener kinds come out as ResilientClassDelegates and share instantiateDelegate, the client-bean seam. The engine keeps a pre-set listener factory and only injects the expression manager into it, so expression listeners are unaffected and nothing else about the engine changes.

A listener deliberately gets no intent step resilience: execute() is the service-task entry point and Flowable's notify paths never reach it — a listener is not a step, and nothing in the DSL emits one.

The two doc sentences the issue flags (ResilientClassDelegate's javadoc and engine-java/CLAUDE.md) said the seam covered every flowable:class delegate; they now say what it covers and why the listener needed a second registration, in engine-java/CLAUDE.md and engine-intent/CLAUDE.md.

Verification

  • New ResilientListenerFactoryTest — against a real (in-memory) engine: both listener kinds come out as ResilientClassDelegates, the configured factory survives initListenerFactory and gets its expression manager, and the defect case is pinned alongside (without the registration the listener is a stock ClassDelegate).
  • engine-bpm-flowable unit suite — 38 tests, green.
  • JavaDelegateInjectionIT gains a listener process (listeners.bpmn + a constructor-injected ExecutionListener and an @Inject-field TaskListener). All 4 tests green with the fix; the new one fails without it with couldn't instantiate class delegateinjection.CtorInjectedExecutionListener / NoSuchMethodException: <init>() — checked by reverting the one production line and re-running.
  • mvn formatter:validate over the reactor with the cache wiped, and the release-profile javadoc build on engine-bpm-flowable.

Not run locally: the rest of the IT suite.

One adjacent fix rode along, because the new assertion needed it: the IT's runtime-variable helpers were querying variableName, which is the historic endpoint's key — the runtime endpoint serves Flowable's own variable entities, whose key is name. That made assertNoRuntimeVariable vacuous.

Fixes #7222

🤖 Generated with Claude Code

…ean container too (#7222)

#7058 wired a client JavaDelegate through the bean container on both service-task
paths, but only the activity-behaviour factory was registered. Flowable creates a
listener through a different factory: ProcessEngineConfigurationImpl.initListenerFactory
builds its own DefaultListenerFactory carrying a stock DefaultClassDelegateFactory, and
createClassDelegateExecutionListener / createClassDelegateTaskListener call that one. So
a <flowable:executionListener class="..."> or <flowable:taskListener class="..."> written
the way the docs teach - a constructor collaborator, or an @Inject field - either failed
at reflection (no no-arg constructor) or ran with every injected field null, silently:
the #7058 symptom one artefact type over. ResilientClassDelegateFactory.create(String,
List), the overload the listener path uses, was dead code under this configuration.

BpmFlowableConfig now also registers setListenerFactory(new DefaultListenerFactory(
classDelegateFactory)) with the same factory instance, so both listener kinds come out
as ResilientClassDelegates and share instantiateDelegate - the client-bean seam. The
engine keeps a pre-set listener factory and only injects the expression manager into it,
so expression listeners are unaffected. A listener deliberately gets no intent step
resilience: execute() is the service-task entry point and Flowable's notify paths never
reach it, and a listener is not a step.

Verified: new ResilientListenerFactoryTest (both listener kinds against a real engine,
plus the defect case - a stock factory does not yield the resilient delegate), the
engine-bpm-flowable unit suite (38 tests), and a listener process added to
JavaDelegateInjectionIT, which is green with the fix and fails without it with
"couldn't instantiate class delegateinjection.CtorInjectedExecutionListener /
NoSuchMethodException: <init>()". The IT's runtime-variable assertion was reading the
historic endpoint's "variableName" key against the runtime endpoint's "name", which made
assertNoRuntimeVariable vacuous; fixed with it. Docs in engine-java/CLAUDE.md and
engine-intent/CLAUDE.md, whose sentences over-stated the delegate seam's reach, now scope
and extend it.

Fixes #7222

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@delchev
delchev merged commit e78bd9b into master Sep 12, 2026
10 checks passed
@delchev
delchev deleted the issue-7222-listener-bean-injection branch September 12, 2026 05:19
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.

bpm: a flowable:class execution/task listener bypasses the client bean injection seam - only the activity-behaviour factory is wired

1 participant