NullPointerException CustomBatchBuilder

Hi everyone,

We are executing a Camunda batch process in 4 parallel threads at the same time, sometimes we get the following exception:

2021-10-22 02:10:21.294 ERROR 15934 --- [  calculating-3] org.camunda.bpm.engine.context           : ENGINE-16004 Exception while closing command context: null

java.lang.NullPointerException: null
at org.camunda.bpm.engine.impl.batch.BatchEntity.createBatchJobDefinition(BatchEntity.java:279) ~[camunda-engine-7.14.0.jar!/:7.14.0]
at org.camunda.bpm.extension.batch.CustomBatchBuilder.lambda$create$0(CustomBatchBuilder.java:142) [camunda-bpm-custom-batch-core-1.5.1.jar!/:1.5.1]
at org.camunda.bpm.engine.impl.interceptor.CommandExecutorImpl.execute(CommandExecutorImpl.java:28) ~[camunda-engine-7.14.0.jar!/:7.14.0]
at org.camunda.bpm.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:110) ~[camunda-engine-7.14.0.jar!/:7.14.0]
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:46) [camunda-engine-spring-7.14.0.jar!/:7.14.0]
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140) [spring-tx-5.3.9.jar!/:5.3.9]
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:44) [camunda-engine-spring-7.14.0.jar!/:7.14.0]
at org.camunda.bpm.engine.impl.interceptor.ProcessApplicationContextInterceptor.execute(ProcessApplicationContextInterceptor.java:70) [camunda-engine-7.14.0.jar!/:7.14.0]
at org.camunda.bpm.engine.impl.interceptor.CommandCounterInterceptor.execute(CommandCounterInterceptor.java:35) [camunda-engine-7.14.0.jar!/:7.14.0]
at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:33) [camunda-engine-7.14.0.jar!/:7.14.0]
at org.camunda.bpm.extension.batch.CustomBatchBuilder.create(CustomBatchBuilder.java:133) [camunda-bpm-custom-batch-core-1.5.1.jar!/:1.5.1]
at org.camunda.bpm.extension.batch.CustomBatchBuilder.create(CustomBatchBuilder.java:155) [camunda-bpm-custom-batch-core-1.5.1.jar!/:1.5.1]
at com.opessoftware.fatca.duediligence.service.DueDiligenceServiceBase.customBatchBuilderIndividuals(DueDiligenceServiceBase.java:233) [fatca-duediligence-3.0.0-RELEASE-plain.jar!/:na]
at com.opessoftware.fatca.duediligence.service.DueDiligenceServiceBase.calculateInitialStatusIndividuals(DueDiligenceServiceBase.java:206) [fatca-duediligence-3.0.0-RELEASE-plain.jar!/:na]
at com.opessoftware.fatca.backend.service.CalculationAsyncServiceBasic.calculateIndividuals(CalculationAsyncServiceBasic.java:969) [classes!/:na]
at com.opessoftware.fatca.backend.service.CalculationAsyncServiceBasic.executeCalculationBatchForIndividuals(CalculationAsyncServiceBasic.java:811) [classes!/:na]
at com.opessoftware.fatca.backend.service.CalculationAsyncServiceBasic.executeCalculrationBatch(CalculationAsyncServiceBasic.java:783) [classes!/:na]
at com.opessoftware.fatca.backend.service.CalculationAsyncServiceBasic.calculateAccountHoldersList(CalculationAsyncServiceBasic.java:222) [classes!/:na]
at com.opessoftware.fatca.backend.service.CalculationAsyncServiceBasic$$FastClassBySpringCGLIB$$e76bca01.invoke(<generated>) [classes!/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) [spring-core-5.3.9.jar!/:5.3.9]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) [spring-aop-5.3.9.jar!/:5.3.9]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) [spring-aop-5.3.9.jar!/:5.3.9]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) [spring-aop-5.3.9.jar!/:5.3.9]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) [spring-tx-5.3.9.jar!/:5.3.9]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) [spring-tx-5.3.9.jar!/:5.3.9]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) [spring-tx-5.3.9.jar!/:5.3.9]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) [spring-aop-5.3.9.jar!/:5.3.9]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) [spring-aop-5.3.9.jar!/:5.3.9]
at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115) [spring-aop-5.3.9.jar!/:5.3.9]
at org.springframework.aop.interceptor.AsyncExecutionAspectSupport.lambda$doSubmit$3(AsyncExecutionAspectSupport.java:276) ~[spring-aop-5.3.9.jar!/:5.3.9]
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1604) ~[na:1.8.0_265]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) ~[na:1.8.0_265]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) ~[na:1.8.0_265]
at java.lang.Thread.run(Thread.java:748) ~[na:1.8.0_265]

2021-10-22 02:10:21.300 ERROR 15934 --- [  calculating-3] c.o.f.b.s.CalculationAsyncServiceBasic   : ERROR ID: 9c081d2d-c556-4033-bf9f-3796df157951 | EXCEPTION: NullPointerException | STACKTRACE: [java.lang.NullPointerException
at org.camunda.bpm.engine.impl.batch.BatchEntity.createBatchJobDefinition(BatchEntity.java:279)
at org.camunda.bpm.extension.batch.CustomBatchBuilder.lambda$create$0(CustomBatchBuilder.java:142)
at org.camunda.bpm.engine.impl.interceptor.CommandExecutorImpl.execute(CommandExecutorImpl.java:28)
at org.camunda.bpm.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:110)
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:46)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140)
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:44)
at org.camunda.bpm.engine.impl.interceptor.ProcessApplicationContextInterceptor.execute(ProcessApplicationContextInterceptor.java:70)
at org.camunda.bpm.engine.impl.interceptor.CommandCounterInterceptor.execute(CommandCounterInterceptor.java:35)
at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:33)
at org.camunda.bpm.extension.batch.CustomBatchBuilder.create(CustomBatchBuilder.java:133)
at org.camunda.bpm.extension.batch.CustomBatchBuilder.create(CustomBatchBuilder.java:155)
at com.opessoftware.fatca.duediligence.service.DueDiligenceServiceBase.customBatchBuilderIndividuals(DueDiligenceServiceBase.java:233)
at com.opessoftware.fatca.duediligence.service.DueDiligenceServiceBase.calculateInitialStatusIndividuals(DueDiligenceServiceBase.java:206)
at com.opessoftware.fatca.backend.service.CalculationAsyncServiceBasic.calculateIndividuals(CalculationAsyncServiceBasic.java:969)
at com.opessoftware.fatca.backend.service.CalculationAsyncServiceBasic.executeCalculationBatchForIndividuals(CalculationAsyncServiceBasic.java:811)
at com.opessoftware.fatca.backend.service.CalculationAsyncServiceBasic.executeCalculationBatch(CalculationAsyncServiceBasic.java:783)
at com.opessoftware.fatca.backend.service.CalculationAsyncServiceBasic.calculateAccountHoldersList(CalculationAsyncServiceBasic.java:222)
at com.opessoftware.fatca.backend.service.CalculationAsyncServiceBasic$$FastClassBySpringCGLIB$$e76bca01.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115)
at org.springframework.aop.interceptor.AsyncExecutionAspectSupport.lambda$doSubmit$3(AsyncExecutionAspectSupport.java:276)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1604)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
]

Can we know why sometimes is thrown this exception?
It seems like the engine can’t find de custom batch handler by type in the BatchEntity class line:

batchJobHandler = Context.getCommandContext().getProcessEngineConfiguration().getBatchHandlers().get(type);

We going to show the custom batch implementation:

public class CalculationIndividualBatchJobHandler extends CustomBatchJobHandler<BatchAccountHolder> {

    private Logger logger = LoggerFactory.getLogger(CalculationIndividualBatchJobHandler.class);

    private static final String TYPE = "calculation-individual-batch-handler";

    private RuntimeService runtimeService;

    private List<BatchAccountHolder> dataProcessed = new ArrayList<>();

    @Override
    public String getType() {

        return TYPE + "-" + Integer.toHexString(hashCode());
    }

    @Override
    public void execute(final List<BatchAccountHolder> data,
            @SuppressWarnings("unused") CommandContext commandContext) {

        // call engine APIs
        data.forEach(batchAccountHolder -> {

            String processDefinitionKey = batchAccountHolder.getProcessDefinitionKey();
            Individualable individual = batchAccountHolder.getIndividual();
            Accountable account = batchAccountHolder.getAccount();
            Map<String, Object> variables = batchAccountHolder.getVariables();

            this.logger.debug("Initiating camunda process for {}: {} and account: {}", processDefinitionKey,
                    individual.getId(), account.getId());

            try {

                // declare new Process Engine Context
                ProcessEngineContext.requiresNew();

                // call engine APIs
                this.runtimeService.startProcessInstanceByKey(processDefinitionKey,
                        individual.getId() + account.getId(), variables);

            } finally {

                // clear declaration for new Process Engine Context
                ProcessEngineContext.clear();
            }

            this.logger.debug("Finished camunda process for {}: {} and account: {}", processDefinitionKey,
                    individual.getId(), account.getId());
        });

        setDataProcessed(data);
    }
}

And this is the way that we set the custom batch to be executed:

CalculationIndividualBatchJobHandler calculationIndividualBatchJobHandler = new CalculationIndividualBatchJobHandler();

        calculationIndividualBatchJobHandler.setRuntimeService(this.runtimeService);

        // Add the new custom batch handler to the process engine plugin
        this.processEngineFactoryBean.getProcessEngineConfiguration().getProcessEnginePlugins()
                .add(new CustomBatchHandlerPlugin(calculationIndividualBatchJobHandler));

        // Set the batch handlers to custom processEngineConfiguration
        this.processEngineFactoryBean.getProcessEngineConfiguration().getBatchHandlers()
                .put(calculationIndividualBatchJobHandler.getType(), calculationIndividualBatchJobHandler);

        // Set the job handlers to custom processEngineConfiguration
        this.processEngineFactoryBean.getProcessEngineConfiguration().getJobHandlers()
                .put(calculationIndividualBatchJobHandler.getType(), calculationIndividualBatchJobHandler);

        // Execute a custom camunda batch
        CustomBatchBuilder.of(batchAccountHolders)
                .configuration(this.processEngineFactoryBean.getProcessEngineConfiguration())
                .jobHandler(calculationIndividualBatchJobHandler).create();

we hope you can help us find what is generating this error.

Best,
Juan