Skip to content

Add linting rule to check @Scope annotation on Spring Beans used in BPMN processes #54

@khalilmalla95

Description

@khalilmalla95

Description

In Camunda and the Data Sharing Framework (DSF), Spring beans that act as delegates, execution listeners, or task listeners should usually be configured with prototype scope (SCOPE_PROTOTYPE). If the @Scope annotation is omitted, Spring defaults to singleton. That can be acceptable for strictly stateless classes; as soon as someone adds mutable instance state, the risk of race conditions and hard-to-debug concurrency bugs rises sharply—often only visible under load.

Proposed solution

Extend the existing SpringConfigurationLinter so it reuses the current pipeline:

  • Configuration discovery via ProcessPluginDefinition#getSpringConfigurations() (registered @Configuration classes).
  • BPMN reference collection from plugin BPMN files (camunda:class on service/send tasks, message events, execution/task listeners, etc.).
  • Bean coverage by matching each referenced class to a @Bean method’s return type on those registered configurations.
  • Scope check on the covering @Bean method, plus a mutable-field check on the BPMN-referenced implementation class.

For each covered BPMN reference, emit:

  1. SUCCESS@Scope is prototype (e.g. ConfigurableBeanFactory.SCOPE_PROTOTYPE or @Scope("prototype")). No further scope items for that reference in this pass.
  2. ERROR (once) – The bean is effectively singleton (missing @Scope or explicit non-prototype scope such as singleton) and the referenced class has mutable instance fields (not static, not final).
  3. WARN – The covering @Bean has no @Scope (Spring’s default is singleton, which is risky for Camunda hooks unless you are very intentional), or @Scope is explicitly set to a non-prototype value (typically singleton) so the developer consciously acknowledges singleton semantics.

Why this matters

The rule is a safety net for DSF process plugins: it stays aligned with how DSF actually wires beans (only configurations returned by getSpringConfigurations()), and it surfaces dangerous combinations—singleton or default-singleton scope plus mutable instance state—before they bite in production.

Example scenarios

Scenario 1: Missing @ScopeWARN (and ERROR if the delegate/listener class has mutable instance fields)

@Bean
// Missing @Scope annotation
public SetCorrelationKeyListener setCorrelationKeyListener() {
    return new SetCorrelationKeyListener(api);
}

Scenario 2: Explicit singleton → WARN (and ERROR if mutable instance fields exist)

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) // or @Scope("singleton")
public SetCorrelationKeyListener setCorrelationKeyListener() {
    return new SetCorrelationKeyListener(api);
}

Scenario 3: Prototype (recommended) → SUCCESS

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // or @Scope("prototype")
public SetCorrelationKeyListener setCorrelationKeyListener() {
    return new SetCorrelationKeyListener(api);
}

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions