Create incident and stop process flow

Hi,
could someone help me with my question, please?
Is there a way how to create an incident similar to one that is thrown by the engine when the exception happens?
I mean when something wrong happens in the engine, there is an incident and the process is stopped on last wait state. But when I create an incident in the script, the process will continue to the next task and if it reaches the end my created incident is deleted upon the process completion (not by me)
So I need to create the incident and stop the process from moving forward, I was also trying to call the suspend method but this is not helping because right after I call the suspend method in the same script where is the incident created, the token moves forward like he doesn’t care :confused:

createIncidentTest.bpmn (4.0 KB)

Thanks for your help,
Lukas

1 Like

execution.createIncident()

See the delegateexecution java API doc

Hi Stephen,
thank you for your reply, but how is this different from the way that I create the incident in my example? Also, this won’t stop the process either, the behaviour is identical…

My memory was that the incident will stop the process, but if it is not, then your best bet would be to just throw a customized Exception, which will be picked up by the engine and a incident will be created by that thrown error. You could also create your customized incident before your throw the incident as well if you wanted.

Thanks, that is what I am trying right now :slight_smile:, but I was hoping for some “native” function :frowning:

Throwing a error is a native solution. The engine is setup to capture the error.

Incidents are system errors.

If you are trying to handle business errors, then look at using BPMN Errors https://docs.camunda.org/manual/7.9/reference/bpmn20/events/error-events/

I know about errors but I am not trying to handle business error. It is more like technical error but there is no BE system involved so I have to raise an exception in the process. I already solve it with this code:
throw new java.lang.Exception(“exception messaget”);

1 Like

@lhofer87 were you able to figure out how to do this without having to throw an Exception?

Hi Zaheena,
no, I am using the code in the “solution” post. I don’t know how to stop process flow like there is an incident but without it. This code:
throw new java.lang.Exception("message");
Simply creates the incident and stop the process which is ok :).

But doesn’t it also retry the code in the task 3 times (which is the default unless you set it to something else)?

I dont know :frowning: we should try it :slight_smile:

Hi all,

I recently tried more or less the same from a JavaDelegate. The Code inside the catch block for ProcessVariableUndefinedException does not work. No incident is created, which would be visible in Cockpit. Instead we get a plethora of Database Exceptions. The main point is, that there are types of errors, which are not business errors (BpmnError) and therefore should become an incident without going through a pointless retry schedule.

import org.camunda.bpm.engine.delegate.BpmnError;
import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.camunda.bpm.engine.delegate.JavaDelegate;

public abstract class MyDelegate implements JavaDelegate {

  abstract void executeInternal(DelegateExecution execution);

  @Override
  public void execute(DelegateExecution execution) throws Exception {
    try {
      executeInternal(execution);
    } catch (MyBusinessError mbe) {
      throw new BpmnError("MyBusinessError", mbe.getMessage());
    } catch (ProcessVariableUndefinedException e) {
      // This error is not recoverable.
      // It does not make sense to enter a retry schedule here.
      // Instead consider creating an incident programmatically, without throwing.
      execution.createIncident("executeInternal", "ProcessVariableDoesNotExist", e.getMessage());
      execution.getProcessEngine().getRuntimeService().suspendProcessInstanceById(execution.getProcessInstanceId());
    }
  }
}

class MyBusinessError extends RuntimeException {
  public MyBusinessError(String message) {
    super(message);
  }
}

class ProcessVariableUndefinedException extends RuntimeException {
  public ProcessVariableUndefinedException(String message) {
    super(message);
  }
}

@cachescrubber you code works, but your last line of the catch is wrong. You are trying to suspend the process instance that is currently undergoing execution. So you have a race event going on, where the process instance has not saved state yet, and that same instance is trying to suspend itself.

@cachescrubber you can update your code to something like this and the process will suspend as expected

    @Override
    public void execute(DelegateExecution execution) throws Exception {
        
       String id = execution.getProcessInstanceId();
        
        try {
            Object dog = execution.getVariable("dog");
            if (dog == null) {
                throw new RuntimeException("WHAT!!?");
            }
        } catch (RuntimeException e) {
            execution.createIncident("executeInternal", "ProcessVariableDoesNotExist", e.getMessage());
            Context.getCommandContext().getTransactionContext().addTransactionListener(TransactionState.COMMITTED, commandContext -> {
                runtimeService.suspendProcessInstanceById(id);
            });
        }
    }

Note that this will suspend the process after the transaction is completed and committed. So this means if you had N tasks after your incident task, and those N tasks were all sync and part of the same transaction as the trask that through the incident, then those N tasks will complete, and after that the process instance will be suspended.

Also take a look at the getTransactionContext().rollback() + adding a listener using TransactionState.ROLLED_BACK. You might be able to do a listener for post roll back and then trigger a roll back + have your incident logged. That way you dont have the process continue past the task that started the incident. Just a idea, have not tested that. But you will also have to keep in mind the impacts of a roll back. In the example of the BPMN image above, a rollback would have caused the process to not start, and thus unable to create a incident. You are likely going to have to recreate the same logic as core camunda: Rollback, create incident on the task that the rollback rolled back to.

Hi @StephenOTT,

thanks for your input, I’ll give it a try later today!

I think the TransactionState.COMMITTED solution is problematic in case of non asyncronous continuations so I will investigate the second solution you proposed.

Another way I could think of would be to simply set the maximum retry count to zero before re-throwing the exception - and let the engine do the right thing. Would this be possible?

best,
Lars

Seems found solution, thanks to the answers above

Using Kotlin:

private fun createIncident(e: Exception) {
    with(Context.getCommandContext()) {
        if (currentJob == null) throw e // not async execution

        transactionContext.addTransactionListener(TransactionState.ROLLED_BACK) {
            val jobId = currentJob.id
            processEngineConfiguration.commandExecutorTxRequired.execute {
                it.jobManager.findJobById(jobId).apply {
                    exceptionMessage = e.message ?: "not_recoverable"
                    retries = 0
                }
            }
        }
    }
    throw e
}

Inside your task you can execute that code:

if (isNotRecoverableError(e)) {
    // This error is not recoverable.
    // There is no sense to do retries here.
    createIncident(toBeThrown)
}
throw toBeThrown

Side note :
Seems the best solution here would be to implement another FailedJobCommandFactory instead of DefaultFailedJobCommandFactory.