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.
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.
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
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
| Aspect | Mount a volume | Build a custom image |
|---|---|---|
| Rebuild needed to change JARs | No | Yes |
| Best suited for | Development, quick trials | Production, CI/CD |
| Reproducible / self-contained | No (depends on host) | Yes |
| Transitive dependency handling | Manual | Can 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
ClassNotFoundExceptionorNoClassDefFoundError, which points to a JAR that still needs to be added.

