Zeebe exporter class cast exception

Hey guys, I’m working on a custom zeebe exporter. I had some issues with it before

Now my exporter has become a part of a multi module gradle project (I migrated it to maven) and unfortunately on zeebe startup I get this exception

java.lang.IllegalStateException: Failed to execute CommandLineRunner
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:774) [spring-boot-3.1.2.jar:3.1.2]
        at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:755) [spring-boot-3.1.2.jar:3.1.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:319) [spring-boot-3.1.2.jar:3.1.2]
        at io.camunda.zeebe.broker.StandaloneBroker.main(StandaloneBroker.java:82) [camunda-zeebe-8.3.0-alpha4.jar:8.3.0-alpha4]
Caused by: java.lang.IllegalStateException: Failed to load exporter with configuration: ExporterCfg{, jarPath='/usr/local/zeebe/lib/zeebe-exporter-demo-1.0-SNAPSHOT-jar-with-dependencies.jar', className='io.zeebe.exporters.Demo.DemoExporter', args={logLevel=debug, prettyPrint=false}}
        at io.camunda.zeebe.broker.Broker.buildExporterRepository(Broker.java:149) ~[zeebe-broker-8.3.0-alpha4.jar:8.3.0-alpha4]
        at io.camunda.zeebe.broker.Broker.<init>(Broker.java:70) ~[zeebe-broker-8.3.0-alpha4.jar:8.3.0-alpha4]
        at io.camunda.zeebe.broker.Broker.<init>(Broker.java:49) ~[zeebe-broker-8.3.0-alpha4.jar:8.3.0-alpha4]
        at io.camunda.zeebe.broker.StandaloneBroker.run(StandaloneBroker.java:91) ~[camunda-zeebe-8.3.0-alpha4.jar:8.3.0-alpha4]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:771) ~[spring-boot-3.1.2.jar:3.1.2]
        ... 3 more
Caused by: io.camunda.zeebe.broker.exporter.repo.ExporterLoadException: Cannot load exporter [demoexporter]: cannot load specified class
        at io.camunda.zeebe.broker.exporter.repo.ExporterRepository.load(ExporterRepository.java:82) ~[zeebe-broker-8.3.0-alpha4.jar:8.3.0-alpha4]
        at io.camunda.zeebe.broker.Broker.buildExporterRepository(Broker.java:147) ~[zeebe-broker-8.3.0-alpha4.jar:8.3.0-alpha4]
        at io.camunda.zeebe.broker.Broker.<init>(Broker.java:70) ~[zeebe-broker-8.3.0-alpha4.jar:8.3.0-alpha4]
        at io.camunda.zeebe.broker.Broker.<init>(Broker.java:49) ~[zeebe-broker-8.3.0-alpha4.jar:8.3.0-alpha4]
        at io.camunda.zeebe.broker.StandaloneBroker.run(StandaloneBroker.java:91) ~[camunda-zeebe-8.3.0-alpha4.jar:8.3.0-alpha4]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:771) ~[spring-boot-3.1.2.jar:3.1.2]
        ... 3 more
Caused by: java.lang.ClassCastException: class io.zeebe.exporters.Demo.DemoExporter
        at java.lang.Class.asSubclass(Unknown Source) ~[?:?]
        at io.camunda.zeebe.broker.exporter.repo.ExporterRepository.load(ExporterRepository.java:80) ~[zeebe-broker-8.3.0-alpha4.jar:8.3.0-alpha4]
        at io.camunda.zeebe.broker.Broker.buildExporterRepository(Broker.java:147) ~[zeebe-broker-8.3.0-alpha4.jar:8.3.0-alpha4]
        at io.camunda.zeebe.broker.Broker.<init>(Broker.java:70) ~[zeebe-broker-8.3.0-alpha4.jar:8.3.0-alpha4]
        at io.camunda.zeebe.broker.Broker.<init>(Broker.java:49) ~[zeebe-broker-8.3.0-alpha4.jar:8.3.0-alpha4]
        at io.camunda.zeebe.broker.StandaloneBroker.run(StandaloneBroker.java:91) ~[camunda-zeebe-8.3.0-alpha4.jar:8.3.0-alpha4]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:771) ~[spring-boot-3.1.2.jar:3.1.2]
        ... 3 more

Any thoughts on this one? I’m still building and deploying with java 11 so I hope it’s not a big deal (since Zeebe requires jdk+17). With maven and as a separate module it worked perfectly fine for me.

I was able to trace it in the zeebe codebase that there is a problem with exporter registartion and this is the line throwing exception:

try {
  final Class<?> specifiedClass = classLoader.loadClass(config.getClassName());
  --> exporterClass = specifiedClass.asSubclass(Exporter.class); <---
} catch (final ClassNotFoundException | ClassCastException e) {
  throw new ExporterLoadException(id, "cannot load specified class", e);
}

But I’m almost running out of ideas so any help is appreciated. Thanks.

2 Likes

Hey Michal :wave:

Just a guess as the stack trace really doesn’t give away much: Is your exporter compiled with the same version of Zeebe that you are trying to run it with?
I think if your dependency on Zeebe is for example 8.2.12 but you are deploying it to a Zeebe broker in version 8.2.13, you’d get a ClassCastException.

Did you manage to move past this? Running into the same now with Zeebe 8.5.0. Tried compiling the exporter to both jdk 17 and 21, with no success.

1 Like

I am facing the same issue too. did you guys figure out a way forward?

I was able to resolve my errors, posting in case someone else faces the same:
the subclass error is a generic error. for me the underneath cause was that I had tenant index custom configuration, and index name has to be in all smallcase.

Can you check that you don’t have the class:

io/camunda/zeebe/exporter/api/Exporter.class

in your generated jar?

Use:
jar -tf your-exporter-jar-with-dependencies.jar | grep Exporter

If you have it, then exclude the class or the whole io.camunda:zeebe-exporter-api package from the assembly

Can you go through this ,
Version might be deprecated, but procedure is same

Zeebe Exporters

Exporters allow you to tap into the Zeebe event stream on a partition and export selected events to other systems. You can filter events, perform transformations, and even trigger side-effects from an exporter.

Read a two-part series about building Zeebe exporters on the Zeebe blog: Part One | Part Two.

Important Things to Know About Exporters

  1. Resource Impact:

    • Exporters run in the same JVM as the broker.
    • Intensive computation in an exporter impacts broker throughput.
    • Perform minimal processing in the exporter and handle further transformations in another system.
  2. Disk Management:

    • A misbehaving exporter can cause broker disks to fill up.
    • The event log truncates only up to the earliest exporter position.
    • Ensure exporters advance their positions in the stream to prevent disk issues.
  3. Failure Planning:

    • Design for connectivity failures with external systems.

Building an Exporter

Create a Maven Project

mvn archetype:generate -DgroupId=io.zeebe \
    -DartifactId=zeebe-exporter-demo \
    -DarchetypeArtifactId=maven-archetype-quickstart  \
    -DinteractiveMode=false

Add Dependency

Update pom.xml:

<dependencies>
    <dependency>
        <groupId>io.camunda</groupId>
        <artifactId>zeebe-exporter-api</artifactId>
        <version>8.5.0</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>3.8.1</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Rename and Modify File

  1. Rename src/main/java/io.zeebe/App.java to DemoExporter.java.
  2. Edit DemoExporter.java to implement the Exporter interface:
import io.camunda.zeebe.exporter.api.Exporter;
import io.camunda.zeebe.exporter.api.context.Context;
import io.camunda.zeebe.exporter.api.context.Controller;
import io.camunda.zeebe.protocol.record.Record;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DemoExporter implements Exporter {
    private static final Logger log = LoggerFactory.getLogger(DemoExporter.class);
    private Controller controller;

    @Override
    public void configure(Context context) {
    }

    @Override
    public void open(Controller controller) {
        log.info("Exporter Open");
        this.controller = controller;
    }

    @Override
    public void close() {
    }

    @Override
    public void export(Record record) {
        log.info("Hello from exporter");
        log.info("--- Zeebe record : {} ---", record.toString());
        this.controller.updateLastExportedRecordPosition(record.getPosition());
    }
}

Exporter Lifecycle

1. configure

  • Allows the exporter to read configuration from the zeebe.cfg.toml file.
  • If an error occurs, the broker halts.

2. open

  • Provides a reference to the Controller for marking records as exported.

3. close

  • Used for cleanup during broker shutdown.

4. export

  • Called when a record is available for export.
  • Marks the record as exported.

Deploy Exporters to Zeebe

Build Exporter

Run the command:

mvn package

Update Zeebe Configuration

  1. Move the generated .jar file (without dependencies) to the Zeebe location.
  2. Update the Zeebe configuration file:
exporters:
  incident:
    className: io.zeebe.camunda.exporter.incident.DemoExporter
    jarPath: /exporters/zeebe-exporter-demo-1.0-SNAPSHOT.jar

Broker Configuration Example

zeebe:
  broker:
    gateway:
      enable: true
      network:
        port: 26500
      security:
        enabled: false

    network:
      host: 0.0.0.0

    data:
      directory: data
      logSegmentSize: 128MB
      snapshotPeriod: 15m

    cluster:
      clusterSize: 1
      replicationFactor: 1
      partitionsCount: 1

    threads:
      cpuThreadCount: 2
      ioThreadCount: 2

    exporters:
      incident:
        className: io.zeebe.camunda.exporter.incident.DemoExporter
        jarPath: /exporters/zeebe-exporter-demo-1.0-SNAPSHOT.jar

Verify Logs

Check broker logs for exporter messages:

2024-09-05 13:16:18.970 [Broker-0] [zb-fs-workers-0] [Exporter-1] INFO
  io.zeebe.camunda.exporter.incident.DemoExporter - Exporter Open
2024-09-05 13:16:18.981 [Broker-0] [zb-fs-workers-0] [Exporter-1] INFO
  io.zeebe.camunda.exporter.incident.DemoExporter - Hello from exporter
2024-09-05 13:16:18.983 [Broker-0] [zb-fs-workers-0] [Exporter-1] INFO
  io.zeebe.camunda.exporter.incident.DemoExporter - --- Zeebe record : {...}