Skip to main content

Injecting Custom Java Libraries

The official Flowable Docker images can be extended with your own Java libraries, for example custom service tasks, event listeners, expression functions or third party integrations. This page describes the two supported ways to do that and when to use each.

note

This page covers how to get a JAR into the image. For how to write the Java code itself, see Java extensions.

How the classpath works

When a Flowable image starts, every JAR in the /additional-classpath/ directory inside the container is added to the application classpath. Both approaches below come down to the same thing: getting your JARs into that directory.

Note that /additional-classpath/ is not a dependency resolver, so you must add your JAR together with all of its transitive dependencies (those already shipped with the image are the exception). Approach 2 resolves the full set automatically with Maven.

If your extension depends on Flowable Java APIs, build it against the same Flowable version as the image to avoid linkage errors such as NoSuchMethodError or NoClassDefFoundError at runtime. Those APIs are already in the image, so declare them with Maven scope provided: they are then used only for compilation and not copied into /additional-classpath/.

Approach 1: Mount a volume

Mounting keeps the stock Flowable image unchanged and provides the JARs from the host at runtime. This is the quickest option and is well suited to local development and quick trials.

Using docker run

Mount a single JAR into /additional-classpath/:

docker run \
-v /path/on/host/my-extension.jar:/additional-classpath/my-extension.jar:ro \
repo.flowable.com/docker/flowable/flowable-work

To mount several JARs at once, put them in a directory on the host and mount the whole directory:

docker run \
-v /path/on/host/libs:/additional-classpath:ro \
repo.flowable.com/docker/flowable/flowable-work

The :ro suffix mounts the JARs read-only, which is recommended since the application only needs to read them.

Using Docker Compose

The same mount expressed in a docker-compose.yml file:

services:
flowable-work:
image: repo.flowable.com/docker/flowable/flowable-work
volumes:
- ./libs:/additional-classpath:ro

Here the local ./libs directory (next to the compose file) is mounted into /additional-classpath. Drop your JAR and its dependencies into ./libs and restart the container.

tip

When mounting a directory, make sure it contains your JAR and its transitive dependencies. See How the classpath works.

Approach 2: Build a custom image

Building your own image on top of a Flowable image produces a single, self-contained artifact that carries the extra libraries with it. This is the recommended option for production and CI/CD pipelines, because the result is reproducible and does not depend on files present on the host.

Copying pre-built JARs

If you already have the JAR and all of its dependencies available locally, a minimal Dockerfile is enough:

FROM repo.flowable.com/docker/flowable/flowable-work

# Copy your JARs (and their dependencies) into the additional classpath
COPY ./libs/*.jar /additional-classpath/

Build and run it:

docker build -t my-company/flowable-work .
docker run my-company/flowable-work
tip

Pin the base image to the exact Flowable version you run in production, for example repo.flowable.com/docker/flowable/flowable-work:<version>, so your custom image is built on a known, reproducible base.

Resolving dependencies with Maven

To avoid collecting transitive dependencies by hand, let Maven resolve them in a build stage and copy the resolved set into the final image. Create a small pom.xml that declares the dependencies you want to add:

<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>com.example</groupId>
<artifactId>flowable-custom-libraries</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>

<dependencies>
<!-- Your own extension JAR -->
<dependency>
<groupId>com.example</groupId>
<artifactId>my-extension</artifactId>
<version>1.0.0</version>
</dependency>

<!-- Any additional third party libraries -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.11.0</version>
</dependency>
</dependencies>
</project>

Any dependency on a Flowable API should be declared with provided scope and the same version as the image (see How the classpath works), so it is used for compilation but not copied into the image.

Then use a multi-stage Dockerfile. The first stage runs Maven's dependency:copy-dependencies to collect the declared dependencies together with their transitive dependencies into one directory; the final stage copies that directory into /additional-classpath/. The -DincludeScope=runtime option limits the copied JARs to the compile and runtime dependencies, so anything declared with provided scope (such as the Flowable APIs already in the image) is left out:

# Build stage: resolve the dependency set.
# This stage only downloads and copies JARs, it does not compile any code,
# so any recent Maven image works here.
FROM maven:3 AS deps
WORKDIR /build
COPY pom.xml .
RUN mvn -B dependency:copy-dependencies -DoutputDirectory=/build/libs -DincludeScope=runtime

# Final stage: extend the Flowable image
FROM repo.flowable.com/docker/flowable/flowable-work
COPY --from=deps /build/libs/*.jar /additional-classpath/

Build it the same way as before:

docker build -t my-company/flowable-work .

Because Maven pulls in the full transitive dependency tree, this approach prevents the missing-dependency errors described under How the classpath works.

Choosing an approach

AspectMount a volumeBuild a custom image
Rebuild needed to change JARsNoYes
Best suited forDevelopment, quick trialsProduction, CI/CD
Reproducible / self-containedNo (depends on host)Yes
Transitive dependency handlingManualCan be automated with Maven

Verifying the libraries are loaded

After starting the container, confirm your JARs were picked up:

  • Check that the files are present in the classpath directory:

    docker exec <container> ls /additional-classpath/
  • Watch the application startup logs for the behaviour your extension provides (for example a registered service task, event listener or bean). A missing dependency typically surfaces here as a ClassNotFoundException or NoClassDefFoundError, which points to a JAR that still needs to be added.