NullpointerException while completing a Task

Hi there,

we are trying to switch to camunda 7.8 but we are facing a NPE while trying to complete a UserTask via REST-API. The reason why this happens is rather curious:

We registered a taskNotificationListener which sends notifications via websockets when a task gets completed (via taskService.getIdentityLinksForTask(delegateTask.getId())). This method uses the GetIdentityLinksForTaskCmd which adds programmatically a new IdentityLinkObject to the IdentityList. Thankfully the comment in this method explains the NPE. Camunda then tries to complete and delete this task with all associated Identitylinks (TaskManager:L84). Since the TaskEntity is already cached in dbEntityCache AND the (dirty) List with IdentityLinkEntities is already loaded, a IdentityLinkEntity with ID [NULL] gets passed to the DbOperationsManager and the underlying DbEntityOperationComparator where it goes Boom. I’ve added the relevant code and the stacktrace below…

A solution for this issue would be returning a copy of List in the GetIdentityForTaskCmd.

the execute method in GetIdentityLinksForTaskCmd loads the IdentityLinks and adds a new Entity with ID [NULL] to the List.

    List<IdentityLink> identityLinks = (List) task.getIdentityLinks();

    // assignee is not part of identity links in the db.
    // so if there is one, we add it here.
    // @Tom: we discussed this long on skype and you agreed ;-)
    // an assignee *is* an identityLink, and so must it be reflected in the API
    //
    // Note: we cant move this code to the TaskEntity (which would be cleaner),
    // since the task.delete cascased to all associated identityLinks
    // and of course this leads to exception while trying to delete a non-existing identityLink
    if (task.getAssignee() != null) {
      IdentityLinkEntity identityLink = new IdentityLinkEntity();
      identityLink.setUserId(task.getAssignee());
      identityLink.setTask(task);
      identityLink.setType(IdentityLinkType.ASSIGNEE);
      identityLinks.add(identityLink);
    }
    if (task.getOwner() != null) {
      IdentityLinkEntity identityLink = new IdentityLinkEntity();
      identityLink.setUserId(task.getOwner());
      identityLink.setTask(task);
      identityLink.setType(IdentityLinkType.OWNER);
      identityLinks.add(identityLink);
    }

    return (List) task.getIdentityLinks();

org.camunda.bpm.engine.impl.persistence.entity.TaskEntity,delete reuses this cached List

public void deleteIdentityLinks(boolean withHistory) {
    List<IdentityLinkEntity> identityLinkEntities = getIdentityLinks();
    for (IdentityLinkEntity identityLinkEntity : identityLinkEntities) {
      fireDeleteIdentityLinkAuthorizationProvider(identityLinkEntity.getType(),
        identityLinkEntity.getUserId(), identityLinkEntity.getGroupId());
      identityLinkEntity.delete(withHistory);
    }
    isIdentityLinksInitialized = false;
  }

org.camunda.bpm.engine.impl.db.entitymanager.operation.DbOperationManager tries to sort the delete operations

  protected SortedSet<DbEntityOperation> getDeletesByType(Class<? extends DbEntity> type, boolean create) {
    SortedSet<DbEntityOperation> deletesByType = deletes.get(type);
    if(deletesByType == null && create) {
      deletesByType = new TreeSet<DbEntityOperation>(MODIFICATION_OPERATION_COMPARATOR);
      deletes.put(type, deletesByType);
    }
    return deletesByType;
  }

org.camunda.bpm.engine.impl.db.entitymanager.operation.comparator.DbEntityOperationComparator

public int compare(DbEntityOperation firstOperation, DbEntityOperation secondOperation) {

    if(firstOperation.equals(secondOperation)) {
      return 0;
    }

    DbEntity firstEntity = firstOperation.getEntity();
    DbEntity secondEntity = secondOperation.getEntity();

    return firstEntity.getId().compareTo(secondEntity.getId());
  }

The NPE happens because of an IndentityLinkEntity with ID [NULL]:

Caused by: java.lang.NullPointerException: null
	at java.lang.String.compareTo(String.java:1155) ~[na:1.8.0_74]
	at org.camunda.bpm.engine.impl.db.entitymanager.operation.comparator.DbEntityOperationComparator.compare(DbEntityOperationComparator.java:35) ~[camunda-engine-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.impl.db.entitymanager.operation.comparator.DbEntityOperationComparator.compare(DbEntityOperationComparator.java:24) ~[camunda-engine-7.8.0.jar:7.8.0]
	at java.util.TreeMap.put(TreeMap.java:552) ~[na:1.8.0_74]
	at java.util.TreeSet.add(TreeSet.java:255) ~[na:1.8.0_74]
	at org.camunda.bpm.engine.impl.db.entitymanager.operation.DbOperationManager.addOperation(DbOperationManager.java:75) ~[camunda-engine-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.performEntityOperation(DbEntityManager.java:572) ~[camunda-engine-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.flushCachedEntity(DbEntityManager.java:451) ~[camunda-engine-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.flushEntityCache(DbEntityManager.java:417) ~[camunda-engine-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.flush(DbEntityManager.java:283) ~[camunda-engine-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.impl.interceptor.CommandContext.flushSessions(CommandContext.java:203) ~[camunda-engine-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.impl.interceptor.CommandContext.close(CommandContext.java:132) ~[camunda-engine-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:113) ~[camunda-engine-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:42) ~[camunda-engine-spring-7.8.0.jar:7.8.0]
	at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133) ~[spring-tx-4.3.12.RELEASE.jar:4.3.12.RELEASE]
	at org.camunda.bpm.engine.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:40) ~[camunda-engine-spring-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.impl.interceptor.ProcessApplicationContextInterceptor.execute(ProcessApplicationContextInterceptor.java:66) ~[camunda-engine-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:30) ~[camunda-engine-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.impl.TaskServiceImpl.complete(TaskServiceImpl.java:173) ~[camunda-engine-7.8.0.jar:7.8.0]
	at org.camunda.bpm.engine.rest.sub.task.impl.TaskResourceImpl.complete(TaskResourceImpl.java:96) ~[camunda-engine-rest-core-7.8.0.jar:7.8.0]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_74]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_74]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_74]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_74]

Well, that was a fun debugging session :slight_smile:

Cheers,
Jürgen

Link to JIRA-Issue: https://app.camunda.com/jira/browse/CAM-8494

Good catch :+1:

I agree that the command should return a copy of the list.

Cheers,
Thorben

edit: A related bug would be that if you call the command twice, you will have two assignee entries in the list.

Hi all,

It looks like we’ve run into the very same issue. Could anyone point me out how would the workaround look like?

Thanks in advance
Jan

We seem to have hit the exact same bug, and it’s really easy to reproduce.

The only configuration done is to set a candidate group on task 2.

image

Now, run this from the cockpit, and complete the “emergency task” (also from the cockpit). This will crash with a NPE as follows:

18-Mar-2019 18:14:57.467 WARNING [http-nio-8080-exec-2] org.camunda.bpm.engine.rest.exception.ExceptionHandler.toResponse java.lang.NullPointerException
	at java.lang.String.compareTo(String.java:1155)
	at org.camunda.bpm.engine.impl.db.entitymanager.operation.comparator.DbEntityOperationComparator.compare(DbEntityOperationComparator.java:35)
	at org.camunda.bpm.engine.impl.db.entitymanager.operation.comparator.DbEntityOperationComparator.compare(DbEntityOperationComparator.java:24)
	at java.util.TreeMap.put(TreeMap.java:552)
	at java.util.TreeSet.add(TreeSet.java:255)
	at org.camunda.bpm.engine.impl.db.entitymanager.operation.DbOperationManager.addOperation(DbOperationManager.java:75)
	at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.performEntityOperation(DbEntityManager.java:572)
	at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.flushCachedEntity(DbEntityManager.java:451)
	at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.flushEntityCache(DbEntityManager.java:417)
	at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.flush(DbEntityManager.java:283)
	at org.camunda.bpm.engine.impl.interceptor.CommandContext.flushSessions(CommandContext.java:203)
	at org.camunda.bpm.engine.impl.interceptor.CommandContext.close(CommandContext.java:132)
	at org.camunda.bpm.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:113)
	at org.camunda.bpm.engine.impl.interceptor.ProcessApplicationContextInterceptor.execute(ProcessApplicationContextInterceptor.java:66)
	at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:30)
	at org.camunda.bpm.engine.impl.FormServiceImpl.submitTaskForm(FormServiceImpl.java:88)
	at org.camunda.bpm.engine.rest.sub.task.impl.TaskResourceImpl.submit(TaskResourceImpl.java:116)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:167)
	at org.jboss.resteasy.core.ResourceMethod.invokeOnTarget(ResourceMethod.java:257)
	at org.jboss.resteasy.core.ResourceMethod.invoke(ResourceMethod.java:222)
	at org.jboss.resteasy.core.ResourceLocator.invokeOnTargetObject(ResourceLocator.java:159)
	at org.jboss.resteasy.core.ResourceLocator.invoke(ResourceLocator.java:107)
	at org.jboss.resteasy.core.ResourceLocator.invokeOnTargetObject(ResourceLocator.java:154)
	at org.jboss.resteasy.core.ResourceLocator.invoke(ResourceLocator.java:92)
	at org.jboss.resteasy.core.SynchronousDispatcher.getResponse(SynchronousDispatcher.java:542)
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:524)
	at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:126)
	at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:208)
	at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:55)
	at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:50)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:292)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
	at org.camunda.bpm.engine.rest.filter.CacheControlFilter.doFilter(CacheControlFilter.java:41)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
	at org.camunda.bpm.engine.rest.filter.EmptyBodyFilter.doFilter(EmptyBodyFilter.java:95)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
	at org.camunda.bpm.webapp.impl.security.filter.SecurityFilter.doFilterSecure(SecurityFilter.java:67)
	at org.camunda.bpm.webapp.impl.security.filter.SecurityFilter.doFilter(SecurityFilter.java:51)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
	at org.camunda.bpm.webapp.impl.security.auth.AuthenticationFilter$1.execute(AuthenticationFilter.java:58)
	at org.camunda.bpm.webapp.impl.security.auth.AuthenticationFilter$1.execute(AuthenticationFilter.java:56)
	at org.camunda.bpm.webapp.impl.security.SecurityActions.runWithAuthentications(SecurityActions.java:40)
	at org.camunda.bpm.webapp.impl.security.auth.AuthenticationFilter.doFilter(AuthenticationFilter.java:56)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:94)
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:504)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
	at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:620)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:502)
	at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1132)
	at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:684)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1539)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1495)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
	at java.lang.Thread.run(Thread.java:748)

BPMN file: https://pastebin.com/s7Yn2Gj5

I haven’t been able to reproduce this - can you let me know what your setup is?
Distro, camunda version, database etc?

Also when you say complete via cockpit - do you mean tasklist?

Running in docker with mysql database. Tried versions 7.8, 7.9 and 7.10.

We indeed close the task from the tasklist.

Hold on, we also have some background code that sends status updates trough EventListeners. I will update when I have found out more.

Okay, I’ve managed to strip as much code as possible and still have the execption. We use the getTaskListener to get a list of all candidates for the task, and use that to notify users.

package org.camunda.bpm.destiny;

import org.camunda.bpm.application.ProcessApplication;
import org.camunda.bpm.application.impl.ServletProcessApplication;
import org.camunda.bpm.engine.ProcessEngines;
import org.camunda.bpm.engine.delegate.TaskListener;

@ProcessApplication(name = "destiny-processes")
public class DestinyProcessesApplication extends ServletProcessApplication {

    public TaskListener getTaskListener() {
        return task -> {
            if (TaskListener.EVENTNAME_COMPLETE.equals(task.getEventName())) {
                ProcessEngines
                        .getDefaultProcessEngine()
                        .getTaskService()
                        .getIdentityLinksForTask(
                                task.getId()
                        );
            }
        };
    }
}

Hi Guys,

Is this issue resolved? we are still facing this

@aarushi.m can you provide the stack trace of the error?