Unable to retrieve serialised variable via REST API and Cockpit Viewer

I am having issues querying a process variable via the REST API.
The process variable is set in Java via a strong complex type, but the server is configured to serialise JSON using “defaultSerializationFormat”:

    <properties>
      <property name="history">full</property>
      <property name="databaseSchemaUpdate">true</property>
      <property name="authorizationEnabled">true</property>
      <property name="jobExecutorDeploymentAware">true</property>
      <property name="historyCleanupBatchWindowStartTime">00:01</property>
      <property name="defaultSerializationFormat">application/json</property>
    </properties>

This works fine while I am in Java, however when I want to query the same variable via the REST API (Using Python, but it breaks before any python side deserialisation kicks off). I get the following REST error (Pulled from the logs):

I should also probably note that I make EXTENSIVE use of the embedded forms and have no issues at all working with these variables in javascript via the Camunda forms API which I assume has some form of deserialisation process as per the REST API???

05-Aug-2022 10:51:30.264 SEVERE [http-nio-8080-exec-366] org.camunda.commons.logging.BaseLogger.logError ENGINE-16004 Exception while closing command context: Cannot deserialize object in variable 'customerApplication': SPIN/JACKSON-JSON-01007 Cannot construct java type from string 'za.co.virtualpostman.flow.model.CustomerApplication'
	org.camunda.bpm.engine.ProcessEngineException: Cannot deserialize object in variable 'customerApplication': SPIN/JACKSON-JSON-01007 Cannot construct java type from string 'za.co.virtualpostman.flow.model.CustomerApplication'
		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:105)
		at org.camunda.bpm.engine.impl.persistence.entity.VariableInstanceEntity.getTypedValue(VariableInstanceEntity.java:278)
		at org.camunda.bpm.engine.impl.core.variable.scope.AbstractVariableScope.getTypedValueFromVariableInstance(AbstractVariableScope.java:169)
		at org.camunda.bpm.engine.impl.core.variable.scope.AbstractVariableScope.getVariableTyped(AbstractVariableScope.java:155)
		at org.camunda.bpm.engine.impl.cmd.GetTaskVariableCmdTyped.execute(GetTaskVariableCmdTyped.java:67)
		at org.camunda.bpm.engine.impl.cmd.GetTaskVariableCmdTyped.execute(GetTaskVariableCmdTyped.java:34)
		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.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.TaskServiceImpl.getVariableTyped(TaskServiceImpl.java:276)
		at org.camunda.bpm.engine.impl.TaskServiceImpl.getVariableTyped(TaskServiceImpl.java:263)
		at org.camunda.bpm.engine.rest.sub.task.impl.TaskVariablesResource.getVariableEntity(TaskVariablesResource.java:45)
		at org.camunda.bpm.engine.rest.sub.impl.AbstractVariablesResource.getTypedValueForVariable(AbstractVariablesResource.java:81)
		at org.camunda.bpm.engine.rest.sub.impl.AbstractVariablesResource.getVariable(AbstractVariablesResource.java:73)
		at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
		at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
		at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
		at java.base/java.lang.reflect.Method.invoke(Method.java:564)
		at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:138)
		at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:546)
		at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:435)
		at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$0(ResourceMethodInvoker.java:396)
		at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:358)
		at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:398)
		at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:365)
		at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:150)
		at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:110)
		at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:141)
		at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:110)
		at org.jboss.resteasy.core.ResourceLocatorInvoker.invokeOnTargetObject(ResourceLocatorInvoker.java:141)
		at org.jboss.resteasy.core.ResourceLocatorInvoker.invoke(ResourceLocatorInvoker.java:104)
		at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:440)
		at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:229)
		at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:135)
		at org.jboss.resteasy.core.interception.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:358)
		at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:138)
		at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:215)
		at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:245)
		at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:61)
		at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:56)
		at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
		at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
		at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
		at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
		at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
		at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
		at org.camunda.bpm.engine.rest.security.auth.ProcessEngineAuthenticationFilter.doFilter(ProcessEngineAuthenticationFilter.java:155)
		at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
		at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
		at org.camunda.bpm.engine.rest.filter.CacheControlFilter.doFilter(CacheControlFilter.java:45)
		at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
		at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
		at org.camunda.bpm.engine.rest.filter.EmptyBodyFilter.doFilter(EmptyBodyFilter.java:101)
		at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
		at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
		at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
		at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
		at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
		at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
		at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
		at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:687)
		at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
		at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
		at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
		at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
		at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
		at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1726)
		at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
		at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
		at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
		at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
		at java.base/java.lang.Thread.run(Thread.java:832)
	Caused by: org.camunda.spin.json.SpinJsonDataFormatException: SPIN/JACKSON-JSON-01007 Cannot construct java type from string 'za.co.virtualpostman.flow.model.CustomerApplication'
		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)
		... 75 more
	Caused by: java.lang.IllegalArgumentException: Failed to parse type 'za.co.virtualpostman.flow.model.CustomerApplication' (remaining: ''): Cannot locate class 'za.co.virtualpostman.flow.model.CustomerApplication', problem: za.co.virtualpostman.flow.model.CustomerApplication
		at spinjar.com.fasterxml.jackson.databind.type.TypeParser._problem(TypeParser.java:91)
		at spinjar.com.fasterxml.jackson.databind.type.TypeParser.findClass(TypeParser.java:85)
		at spinjar.com.fasterxml.jackson.databind.type.TypeParser.parseType(TypeParser.java:47)
		at spinjar.com.fasterxml.jackson.databind.type.TypeParser.parse(TypeParser.java:33)
		at spinjar.com.fasterxml.jackson.databind.type.TypeFactory.constructFromCanonical(TypeFactory.java:660)
		at org.camunda.spin.impl.json.jackson.format.JacksonJsonDataFormat.constructJavaTypeFromCanonicalString(JacksonJsonDataFormat.java:165)
		... 79 more

Based on the error I am assuming the process engine if retrieving the process variable and attempting to firstly serialise into the Java type before sending it to deserialise again and send it out.

Interestingly if I view the variable via the cockpit, I see this in serialized mode:

Screen Shot 2022-08-05 at 11.07.11

Which looks as expected, however when I flip over to the “Deserialised” tab I get this:

Screen Shot 2022-08-05 at 11.07.17

Which seems to be the same error.

I am using this Python code to call the REST API

    def getVariableForTaskId(self, taskId: str, variableName: str) -> Dict[str, Any]:
        taskVariablesUrl = self.baseUrl + f"/engine-rest/task/{taskId}/variables/{variableName}"
        response = requests.get(taskVariablesUrl, verify=False, auth=(self.userName, self.password))
        if response.status_code == 200:
            return response.json()

returns a 500 status_code

Have a quick look at Get Task Variable | docs.camunda.org … specifically Example 2

I think you want your Python code to ask the server to not deserialize before sending the response.

The default is for the server to deserialize before sending, which means that the server needs to have the JAR for the java constructor for ‘za.co.virtualpostman.flow.model.CustomerApplication’ which it clearly doesn’t have.

Well this is the odd thing. The server totally DOES have the jar. The entire thing is packaged into the war deployment, which is why I raise the issue of the cockpit giving its odd error message.

I understand the workaround, however it still does not explain why the apps (including the REST API) cannot see the jars given the REST serialization config.
Is there some sort of isolation between the java types and the camunda apps (including the REST API) that the jars dont get shared?

PS: Thank you for the help, it will definitely solve the issue (will try it out tomorrow), I just want to get a deeper understand on the engine.

I know that there’s a LOT of strangeness with classpath loading. Sadly, I’m not enough of an expert on the internals to say why it can’t find the constructor … I just acknowledge that it can’t be found.

Hi @SlappyAUS,

maybe this link helps for some further understanding: Apache Tomcat 9 (9.0.65) - Class Loader How-To

As the Rest API is another webapp, it cannot access classes from a process application.

Hope this helps, Ingo