Search code examples
javaspringcamunda

Stateful Camunda 7 ExternalTaskHandler


is the following safe in terms of ExternalTaskHandler instance reuse by the Camunda 7 engine?

public class ExampleTaskHandler implements ExternalTaskHandler {

    private ExternalTask externalTask;
    private VariableMap taskVariables;

    @Override
    public void execute(ExternalTask externalTask, ExternalTaskService externalTaskService) {
        this.externalTask = externalTask;
        this.taskVariables = new VariableMapImpl();
        try {
            executeTask();
            externalTaskService.complete(externalTask, taskVariables);
        } finally {
            this.externalTask = null;
            this.taskVariables = null;
        }
    }

    private void executeTask() {
        // do stuff and use externalTask and taskVariables
    }

    ...

}

Debugging showed that ExternalTaskHandler instances are being reused/shared between calls. That’s why we null the reference variables in the finally block of execute as a safety measure. Might be overkill.

Are normal instance variables fine or are we advised to use ThreadLocals? Or should we refrain from stateful ExternalTaskHandler implementations altogether? The latter would mean we’d need to pass on stuff in method parameters over and over again, which we would like to avoid.

Thanks for your help!


Solution

  • You are right: Handlers are reusable instances, most likely singleton, and thus must not contain state.

    To your solution: Though it might work, this is a very error prone solution. And hard to read. If your goal is to avoid passing of parameters to private functions, consider two different approaches:

    • worker/executor pattern: in your execute() method you create a new instance of a dedicated worker instance (typically with a single, parameterless method like run()/execute()). All data required by this worker instance is passed and internally you can use them in private helper methods if you like.
    • command pattern: Create a container parameter (typically named command) that holds all data required to execute. Then pass this cmd to all private helper functions instead of a growing number of parameters.