Serialization issue with external tasks

Hey.

We have an embedded Camunda instance running in one service and some external task workers implemented with the Camunda Java client in a client service.

The Camunda service puts process variables into the process and the external workers consume these process variables.

When we use complex types in these process variables, they get serialized into JSON. The problem is that when we try to access the process variables in a worker, the Camunda Java client tries to deserialize the JSON into the original Java types used in the Camunda service for serialization. This produces an exception like below.

The same happens the other way around: when we put a complex process variable into the process in a worker, the Camunda workflow engine tries to deserialize it into the type the worker used for serialization, and fails because the type is not in the classpath (I don’t even know why the workflow engine would need to deserialize a process variable provided by a worker, but that’s a different topic).

Is there a way to configure Camunda (client and server side) to use their own Java classes for deserialization instead of the class provided by the other side? We don’t want to couple client and server by sharing the classes.

The external workers work in a sub process, if that is relevant.

Here’s the stacktrace on the server side:

org.camunda.bpm.engine.ProcessEngineException: Cannot deserialize object in variable 'githubRepository_wPStYSkA': SPIN/JACKSON-JSON-01007 Cannot construct java type from string 'com.atlassian.devops.thirdparty.camunda.internal.ProcessVariables$Repository'
	at org.camunda.bpm.engine.impl.variable.serializer.AbstractSerializableValueSerializer.readValue(AbstractSerializableValueSerializer.java:85)
	at org.camunda.bpm.engine.impl.variable.serializer.AbstractSerializableValueSerializer.readValue(AbstractSerializableValueSerializer.java:31)
	at org.camunda.bpm.engine.impl.persistence.entity.util.TypedValueField.getTypedValue(TypedValueField.java:115)
	at org.camunda.bpm.engine.impl.persistence.entity.VariableInstanceEntity.getTypedValue(VariableInstanceEntity.java:282)
	at org.camunda.bpm.engine.impl.core.variable.scope.AbstractVariableScope.collectVariables(AbstractVariableScope.java:114)
	at org.camunda.bpm.engine.impl.core.variable.scope.AbstractVariableScope.getVariablesTyped(AbstractVariableScope.java:89)
	at org.camunda.bpm.engine.impl.core.variable.scope.AbstractVariableScope.getVariablesTyped(AbstractVariableScope.java:84)
	at org.camunda.bpm.engine.impl.core.variable.scope.AbstractVariableScope.getVariables(AbstractVariableScope.java:80)
	at org.camunda.bpm.engine.impl.core.variable.scope.AbstractVariableScope.getVariables(AbstractVariableScope.java:51)
	at org.camunda.bpm.engine.impl.core.model.CallableElementParameter.applyTo(CallableElementParameter.java:59)
	at org.camunda.bpm.engine.impl.core.model.CallableElement.getVariables(CallableElement.java:131)
	at org.camunda.bpm.engine.impl.core.model.CallableElement.getOutputVariables(CallableElement.java:117)
	at org.camunda.bpm.engine.impl.bpmn.behavior.CallableElementActivityBehavior.getOutputVariables(CallableElementActivityBehavior.java:192)
	at org.camunda.bpm.engine.impl.bpmn.behavior.CallableElementActivityBehavior.passOutputVariables(CallableElementActivityBehavior.java:135)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationProcessEnd.eventNotificationsCompleted(PvmAtomicOperationProcessEnd.java:61)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationProcessEnd.eventNotificationsCompleted(PvmAtomicOperationProcessEnd.java:33)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:66)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:76)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:637)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:612)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:62)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:76)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:637)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:612)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:62)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:628)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:602)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityEnd.execute(PvmAtomicOperationActivityEnd.java:87)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityEnd.execute(PvmAtomicOperationActivityEnd.java:35)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:628)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:602)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityNotifyListenerEnd$1.callback(PvmAtomicOperationActivityNotifyListenerEnd.java:47)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityNotifyListenerEnd$1.callback(PvmAtomicOperationActivityNotifyListenerEnd.java:41)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.continueExecutionIfNotCanceled(PvmExecutionImpl.java:2042)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.dispatchDelayedEventsAndPerformOperation(PvmExecutionImpl.java:1991)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityNotifyListenerEnd.eventNotificationsCompleted(PvmAtomicOperationActivityNotifyListenerEnd.java:41)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityNotifyListenerEnd.eventNotificationsCompleted(PvmAtomicOperationActivityNotifyListenerEnd.java:27)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:66)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:76)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:637)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:612)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:62)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:76)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:637)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:612)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:62)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:76)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:637)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:612)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:62)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:628)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:602)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.end(PvmExecutionImpl.java:393)
	at org.camunda.bpm.engine.impl.bpmn.behavior.NoneEndEventActivityBehavior.execute(NoneEndEventActivityBehavior.java:28)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityExecute$2.callback(PvmAtomicOperationActivityExecute.java:61)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityExecute$2.callback(PvmAtomicOperationActivityExecute.java:50)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.continueIfExecutionDoesNotAffectNextOperation(PvmExecutionImpl.java:2036)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityExecute.execute(PvmAtomicOperationActivityExecute.java:42)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationActivityExecute.execute(PvmAtomicOperationActivityExecute.java:31)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:628)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:602)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl$6.callback(PvmExecutionImpl.java:1975)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl$6.callback(PvmExecutionImpl.java:1972)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.continueExecutionIfNotCanceled(PvmExecutionImpl.java:2042)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.dispatchDelayedEventsAndPerformOperation(PvmExecutionImpl.java:1991)
	at org.camunda.bpm.engine.impl.pvm.runtime.PvmExecutionImpl.dispatchDelayedEventsAndPerformOperation(PvmExecutionImpl.java:1972)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationTransitionNotifyListenerStart.eventNotificationsCompleted(PvmAtomicOperationTransitionNotifyListenerStart.java:60)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationTransitionNotifyListenerStart.eventNotificationsCompleted(PvmAtomicOperationTransitionNotifyListenerStart.java:30)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:66)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:76)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:637)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:612)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:62)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:76)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:637)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:612)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:62)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:76)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:637)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperationSync(ExecutionEntity.java:612)
	at org.camunda.bpm.engine.impl.core.operation.AbstractEventAtomicOperation.execute(AbstractEventAtomicOperation.java:62)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:111)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:628)
	at org.camunda.bpm.engine.impl.persistence.entity.ExecutionEntity.performOperation(ExecutionEntity.java:602)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationTransitionCreateScope.scopeCreated(PvmAtomicOperationTransitionCreateScope.java:38)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationCreateScope.execute(PvmAtomicOperationCreateScope.java:54)
	at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationCreateScope.execute(PvmAtomicOperationCreateScope.java:28)
	at org.camunda.bpm.engine.impl.interceptor.AtomicOperationInvocation.execute(AtomicOperationInvocation.java:99)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.invokeNext(CommandInvocationContext.java:131)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:118)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext$1.call(CommandInvocationContext.java:102)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext$1.call(CommandInvocationContext.java:100)
	at org.camunda.bpm.engine.impl.context.ProcessApplicationClassloaderInterceptor.call(ProcessApplicationClassloaderInterceptor.java:48)
	at org.camunda.bpm.application.AbstractProcessApplication.execute(AbstractProcessApplication.java:121)
	at org.camunda.bpm.application.AbstractProcessApplication.execute(AbstractProcessApplication.java:132)
	at org.camunda.bpm.engine.impl.context.Context.executeWithinProcessApplication(Context.java:206)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performNext(CommandInvocationContext.java:100)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:86)
	at org.camunda.bpm.engine.impl.interceptor.CommandInvocationContext.performOperation(CommandInvocationContext.java:76)
	at org.camunda.bpm.engine.impl.jobexecutor.AsyncContinuationJobHandler.execute(AsyncContinuationJobHandler.java:81)
	at org.camunda.bpm.engine.impl.jobexecutor.AsyncContinuationJobHandler.execute(AsyncContinuationJobHandler.java:40)
	at org.camunda.bpm.engine.impl.persistence.entity.JobEntity.execute(JobEntity.java:133)
	at org.camunda.bpm.engine.impl.cmd.ExecuteJobsCmd.execute(ExecuteJobsCmd.java:110)
	at org.camunda.bpm.engine.impl.cmd.ExecuteJobsCmd.execute(ExecuteJobsCmd.java:43)
	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.engine.impl.jobexecutor.ExecuteJobHelper.executeJob(ExecuteJobHelper.java:57)
	at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobsRunnable.executeJob(ExecuteJobsRunnable.java:110)
	at org.camunda.bpm.engine.impl.jobexecutor.ExecuteJobsRunnable.run(ExecuteJobsRunnable.java:71)
	at org.springframework.cloud.sleuth.instrument.async.TraceRunnable.run(TraceRunnable.java:68)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: org.camunda.spin.json.SpinJsonDataFormatException: SPIN/JACKSON-JSON-01007 Cannot construct java type from string 'com.atlassian.devops.thirdparty.camunda.internal.ProcessVariables$Repository'
	at org.camunda.spin.impl.json.jackson.JacksonJsonLogger.unableToConstructJavaType(JacksonJsonLogger.java:76)
	at org.camunda.spin.impl.json.jackson.format.JacksonJsonDataFormat.constructJavaTypeFromCanonicalString(JacksonJsonDataFormat.java:167)
	at org.camunda.spin.impl.json.jackson.format.JacksonJsonDataFormatMapper.mapInternalToJava(JacksonJsonDataFormatMapper.java:83)
	at org.camunda.spin.plugin.impl.SpinObjectValueSerializer.deserializeFromByteArray(SpinObjectValueSerializer.java:101)
	at org.camunda.bpm.engine.impl.variable.serializer.AbstractObjectValueSerializer.deserializeFromByteArray(AbstractObjectValueSerializer.java:119)
	at org.camunda.bpm.engine.impl.variable.serializer.AbstractSerializableValueSerializer.readValue(AbstractSerializableValueSerializer.java:83)
	... 169 more
Caused by: java.lang.IllegalArgumentException: Failed to parse type 'com.atlassian.devops.thirdparty.camunda.internal.ProcessVariables$Repository' (remaining: ''): Cannot locate class 'com.atlassian.devops.thirdparty.camunda.internal.ProcessVariables$Repository', problem: com.atlassian.devops.thirdparty.camunda.internal.ProcessVariables$Repository
	at com.fasterxml.jackson.databind.type.TypeParser._problem(TypeParser.java:91)
	at com.fasterxml.jackson.databind.type.TypeParser.findClass(TypeParser.java:85)
	at com.fasterxml.jackson.databind.type.TypeParser.parseType(TypeParser.java:47)
	at com.fasterxml.jackson.databind.type.TypeParser.parse(TypeParser.java:33)
	at com.fasterxml.jackson.databind.type.TypeFactory.constructFromCanonical(TypeFactory.java:624)
	at org.camunda.spin.impl.json.jackson.format.JacksonJsonDataFormat.constructJavaTypeFromCanonicalString(JacksonJsonDataFormat.java:165)
	... 173 more

How would you expect the objects to be made accessible “on the other side”? As a JSON string?

I think if the objects are serialized automatically then the other side should try to deserialize them. So the behaviour is as expected I’d say.

If you want to use other classes on the other side then you should transfer the data as a primitive type, e.g. string.

We’ve configured JSON serialization via Spin as per the best practice guide. This is supposed to allow direct serialization of objects as JSON without explicit declaration of the format, for example:

runtimeService.setVariable(processInstance.getId(), "customer", new Customer())

However the engine insists on deserializing the JSON value using the original Java class which is not how JSON serialization should work.

How would you expect the objects to be made accessible “on the other side”? As a JSON string?

I would expect Camunda to allow me to define into which class I want to deserialize the JSON string for a given process variable on the client (worker) side. Otherwise, JSON serialization doesn’t have any benefit over Java’s object serialization.

As for the server side: I’m actually surprised that the Process Engine tries to deserialize a JSON process variable on the server-side. Why doesn’t it just pass it on to the other workers as JSON?

If you want to use other classes on the other side then you should transfer the data as a primitive type, e.g. string.

That’s actually a workaround that we’re thinking about. We could serialize the object into JSON ourselves and then deserialize it ourselves again. But I thought serialization is a job that Camunda does for us…

I understand your expectation, I’d expect the same. I have actually never used the external task pattern myself, just read about it and thought that I understood it.

If the worker tries to deserialize the vars to the same class – isn’t it a problem within the java client and not the camunda engine?

The problem is on both sides:

  1. I set a process variable on the server side and try to read it in the Java client. Deserialization fails on the client side because the original server-side class it not in the classpath. I now worked around this problem by telling the Java client not to deserialize the variable (the false parameter) and then deserialize it with my own ObjectMapper instance:

     val objectValue = getVariableTyped<ObjectValue>(variableName, false)
     val jsonString = objectValue.valueSerialized
     return objectMapper.readValue(jsonString, targetType)
    
  2. I set a process variable in an external client to be consumed in another external task. For some reason, Camunda tries to deserialize the process variable on the server side before passing it to the other external task and it fails because the original client-side class is not in the server’s classpath. We now worked around this problem by using a string-typed process variable that contains JSON and we take care of serialization and deserialization ourselves.

Both are workarounds in my view. With these restrictions, Camunda is not taking full advantage of JSON serialization.

1 Like

If I’ll ever have to implement the external task pattern in Java, I’ll certainly look up this thread to find the solution! I agree with all your points but don’t have a solution now.

We discovered that this problem occurs when you have configured an “all” output variable mapping on a call activity. That is, in the direction of the external task client setting an object value variable and the engine trying to pass the output variables to the parent process. This part of the stack shows where the engine does that based on the “all” configuration:

at org.camunda.bpm.engine.impl.core.variable.scope.AbstractVariableScope.getVariables(AbstractVariableScope.java:80)
at org.camunda.bpm.engine.impl.core.variable.scope.AbstractVariableScope.getVariables(AbstractVariableScope.java:51)
at org.camunda.bpm.engine.impl.core.model.CallableElementParameter.applyTo(CallableElementParameter.java:59)
at org.camunda.bpm.engine.impl.core.model.CallableElement.getVariables(CallableElement.java:131)
at org.camunda.bpm.engine.impl.core.model.CallableElement.getOutputVariables(CallableElement.java:117)
at org.camunda.bpm.engine.impl.bpmn.behavior.CallableElementActivityBehavior.getOutputVariables(CallableElementActivityBehavior.java:192)
at org.camunda.bpm.engine.impl.bpmn.behavior.CallableElementActivityBehavior.passOutputVariables(CallableElementActivityBehavior.java:135)
at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationProcessEnd.eventNotificationsCompleted(PvmAtomicOperationProcessEnd.java:61)
at org.camunda.bpm.engine.impl.pvm.runtime.operation.PvmAtomicOperationProcessEnd.eventNotificationsCompleted(PvmAtomicOperationProcessEnd.java:33)

We still believe this is a bug. The engine shouldn’t try to deserialise the variable at all let alone depend on the original Java class used by the external task client. However we’ve managed to workaround this particular problem by removing the “all” output variable mapping from our call activity.

We have not yet determined why the similar deserialization problem also occurs in the reverse direction: from the engine to the external task client. In that case we’re still working around the issue by setting the JSON as a string variable instead of a JSON object value.

We had the same problem, an exception was thrown (InvalidStateException) and Camunda refused to retrieve a complex/custom java object from the variable map of the external task.
We used the history service as a workaround: using a historic variable instance query we could retrieve the complex variable.