Help to register custom exporter

please help me to register custom exporter
here is what I did till now

package org.rutusoft.email;


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.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public final class EmailNotificationExporter implements Exporter {
    private static Logger logger = LogManager.getLogger(EmailNotificationExporter.class);


    @Override
    public void configure(Context context) throws Exception {
        Exporter.super.configure(context);
    }

    @Override
    public void open(Controller controller) {
        Exporter.super.open(controller);
    }

    @Override
    public void close() {
        Exporter.super.close();
    }

    @Override
    public void export(Record<?> record) {

    }
}

pom.xml file

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>email-exporter</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>email-exporter</name>
  <url>http://maven.apache.org</url>


  <properties>
    <exporter.finalName>${project.artifactId}-${project.version}</exporter.finalName>

    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.camunda/zeebe-exporter-api -->
    <dependency>
      <groupId>io.camunda</groupId>
      <artifactId>zeebe-exporter-api</artifactId>
      <!--      <version>8.4.4</version>-->
      <version>8.5.0-alpha1</version>

    </dependency>


    <dependency>
      <groupId>io.camunda</groupId>
      <artifactId>zeebe-client-java</artifactId>
      <version>8.4.4</version>

      <!--      <scope>test</scope>-->
    </dependency>

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.10.2</version>

      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.testcontainers</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>1.19.6</version>

      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.assertj</groupId>
      <artifactId>assertj-core</artifactId>
      <version>3.25.3</version>

      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j-impl</artifactId>
      <version>2.23.0</version>

      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.23.0</version>

      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.sun.mail/smtp -->
    <dependency>
      <groupId>com.sun.mail</groupId>
      <artifactId>smtp</artifactId>
      <version>2.0.1</version>
    </dependency>

    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>2.6.1</version>
    </dependency>


  </dependencies>

  <build>
    <finalName>${exporter.finalName}</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <configuration>
          <ignoredUnusedDeclaredDependencies>
            <!-- false positive, used as logging output in tests -->
            <unusedDeclaredDependency>org.slf4j:slf4j-simple</unusedDeclaredDependency>
          </ignoredUnusedDeclaredDependencies>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.10.1</version> <!-- Make sure to use the appropriate version -->
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
        <executions>
          <execution>
            <id>standalone</id>
            <goals>
              <goal>single</goal>
            </goals>
            <phase>package</phase>
          </execution>
        </executions>
      </plugin>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <version>3.1.2</version> <!-- Use the appropriate version -->
        <executions>
          <execution>
            <id>copy-dependencies</id>
            <phase>package</phase>
            <goals>
              <goal>copy-dependencies</goal>
            </goals>
            <configuration>
              <outputDirectory>${project.build.directory}/lib</outputDirectory>
            </configuration>
          </execution>
        </executions>
      </plugin>

    </plugins>
  </build>
</project>

zeebe broker config

    exporters:
      email:
        className: EmailNotificationExporter
        # jarPath: exporters/email-exporter-1.0-SNAPSHOT-jar-with-dependencies.jar
        jarPath: exporters/email-exporter-1.0-SNAPSHOT.jar

Hi @sinugaud - how do you have Camunda Self-Managed deployed (Docker, Helm, something else)? This guide shows how to do this with Helm:

If you are using Docker, the approach similar: you need to copy the JAR file to the exporters folder in the Zeebe container. If you search the forums, there are several threads with people installing custom exporters that may be helpful also.

Hi @nathan.loding - I have Camunda Self-Managed setup, I followed that related document,I built a jar put in Zeebe exporter, and configured it, but it does not work, I’m getting class not found
I am assuming that some steps I missed or documents are outdated

Hi @sinugaud - please share as many details as you can:

  • What is the full error?
  • Are you using Helm or Docker (or something else)?
  • What version of Zeebe are you running?
  • Share your Helm or Docker configuration (with secrets removed)

Hi @nathan.loding , at the moment, I’m utilizing the Hazelcast Exporter and have made some adjustments to it.

Give me some time I will share all the details

Hi @nathan.loding

there is are change in configration here is all details:
What I did till now
Here is my pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>Incident-exporter</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Incident-exporter</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>

        <dependency>
            <groupId>io.camunda</groupId>
            <artifactId>zeebe-exporter-api</artifactId>
            <version>8.4.4</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>3.2.2</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.16.2</version>
        </dependency>


        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>

                <version>3.6.0</version>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                    <outputDirectory>${project.build.directory}</outputDirectory>
                </configuration>
                <executions>
                    <execution>
                        <id>assemble-all</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>assemble-for-jib</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/jib</outputDirectory>
                            <finalName>${project.artifactId}</finalName>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

package io.zeebe.incident.exporter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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 io.camunda.zeebe.protocol.record.RecordType;
import org.slf4j.Logger;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;

public class IncidentExporter implements Exporter {
    private Logger logger;

    @Override
    public void configure(Context context) throws Exception {
        logger = context.getLogger();

        Exporter.super.configure(context);
    }

    @Override
    public void open(Controller controller) {
        Exporter.super.open(controller);
    }

    @Override
    public void close() {
        Exporter.super.close();
    }

    @Override
    public void export(Record<?> record) {
        RecordType recordType = record.getRecordType();
        String intent = String.valueOf(record.getIntent());
        logger.info("Record type  String: {}", recordType);
        logger.info("Record intent  String: {}", intent);
        String processInstanceKey;

        Object value = record.getValue();
        String jsonString = value.toString();
        logger.info("Record value  String: {}", jsonString);
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            JsonNode jsonNode = objectMapper.readTree(jsonString);
            processInstanceKey = jsonNode.get("processInstanceKey").asText();
            logger.info("Process Instance Key: " + processInstanceKey);

        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

        // Prepare data for sending via REST
        String dataToSend = prepareData(processInstanceKey, intent, String.valueOf(recordType));
        logger.info("data to send  : {}", dataToSend);
        // Send data via REST API
        sendViaRestApi(dataToSend);

    }

    private String prepareData(String processInstanceKey, String intent, String recordType) {
        // Prepare data as needed
        return "{ \"processInstanceKey\": \"" + processInstanceKey + "\", \"intent\": \"" + intent + "\", \"recordType\": \"" + recordType + "\" }";
    }


//    private byte[] recordToProtobuf(Record record) {
//        final Schema.Record dto = RecordTransformer.toGenericRecord(record);
//        return dto.toByteArray();
//    }
//
//    private byte[] recordToJson(Record record) {
//        final var json = record.toJson();
//        return json.getBytes();
//    }


    private String sendViaRestApi(String data) {
        RestTemplate restTemplate = new RestTemplate();
        String url = "http://localhost:8087/instance/record";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> request = new HttpEntity<>(data, headers);
        logger.info(" response.getStatusCode(): {}", data);


        ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);

        if (response.getStatusCode() == HttpStatus.OK) {
            logger.info(" response: {}", response.getStatusCode());
            return response.getBody();
        } else {
            logger.info(" response: {}", response.getStatusCode());
            throw new RuntimeException("Failed to send data via REST API. Status code: " + response.getStatusCode());
        }


    }
}

Zeebe configuration


    exporters:
      incident:
        className: io.zeebe.incident.exporter.IncidentExporter
        jarPath: /exporters/Incident-exporter-1.0-SNAPSHOT-jar-with-dependencies.jar

  • What is the full error?
java.lang.IllegalStateException: Failed to load exporter with configuration: ExporterCfg{, jarPath='D:\zeebe-camunda\exporters\camunda-zeebe-8.4.4\exporters\Incident-exporter-1.0-SNAPSHOT-jar-with-dependencies.jar', className='io.zeebe.incident.exporter.IncidentExporter', args=null}
        at io.camunda.zeebe.broker.Broker.buildExporterRepository(Broker.java:150) ~[zeebe-broker-8.4.4.jar:8.4.4]

Caused by: java.lang.ClassCastException: class io.zeebe.incident.exporter.IncidentExporter


Caused by: io.camunda.zeebe.broker.exporter.repo.ExporterLoadException: Cannot load exporter [incident]: cannot load specified class


      org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext - Exception thrown from ApplicationListener handling ContextClosedEvent
java.lang.NullPointerException: Cannot invoke "io.camunda.zeebe.broker.Broker.close()" because "this.broker" is null
  • Are you using Helm or Docker (or something else)?
    and -: No I am using self-managed later shift to docker-compose
  • What version of Zeebe are you running?
    8.4.4
  • Share your Helm or Docker configuration (with secrets removed)

Hi @sinugaud - I believe the issue might be the inclusion of Spring. If you look at the existing exporters, including the Hazelcast exporter, they don’t use Spring. I believe Spring creates an executable JAR rather than one that can be imported as a class.

Hi @nathan.loding - I tried adding spring-web maven in hazelcast exporter it is working fine for me , also I tried without spring-web jar in this exporter but am still getting the same error, Are there other methods to register the exporter

Hi,

Any followups on this? My team is facing exactly the same problem.
@nathan.loding, I love to see in your avatar the same baloon that you wore in Berlin :wink:

Thanks,
Jaime

1 Like

Hey @mosteja ,
I did a temporary fix for now I cloned the kafka exporter repo I changed there logic
and it work for now,
let me know if you get any solution regarding this