flowable/flowable-engine · error · FlowableImageException
ProcessDiagramGenerator already closed
Error message
ProcessDiagramGenerator already closed
What it means
DefaultProcessDiagramCanvas buffers the process diagram in memory and is single-use: once close() is called the internal image is released and closed is set to true. generateImage() checks this flag and throws FlowableImageException to prevent rendering from a dead canvas. This signals a lifecycle misuse — callers must generate all images before closing.
Solutions
- Ensure generateImage()/generateBufferedImage() are called before close() on the canvas
- If using try-with-resources, generate the image inside the try block
- If the canvas is shared, remove the premature close() or create a new canvas per render
- Check for double-close paths in custom ProcessDiagramGenerator implementations
Example fix
// before
canvas.close();
InputStream is = canvas.generateImage("png"); // throws
// after
InputStream is = canvas.generateImage("png");
canvas.close(); Defensive patterns
Strategy: try-catch
Validate before calling
if (canvas == null) throw new IllegalStateException("canvas not initialized");
// track close state yourself, or generate before closing:
// InputStream is = canvas.generateImage("png"); canvas.close(); Type guard
boolean isCanvasUsable(DefaultProcessDiagramCanvas c) {
return c != null; // closed flag is private; enforce usage order instead
} Try / catch
try {
InputStream is = canvas.generateImage("png");
} catch (FlowableImageException e) {
// canvas was closed; recreate and regenerate
canvas = new DefaultProcessDiagramCanvas(...);
} Prevention
- Generate all images before closing the canvas
- Never rely on a closed canvas; create a new one instead
- Avoid try-with-resources unless the image is produced inside the try block
- Document canvas ownership in code that shares it
When it happens
Trigger: Calling generateImage(String imageType) after close() has already been invoked on the same DefaultProcessDiagramCanvas instance; reusing a closed canvas from a cached/pooled generator; calling generateImage twice with a close in between.
Common situations: Wrapping the canvas in try-with-resources (which auto-closes it) and then attempting to render afterwards; sharing one canvas across requests where a first handler closed it; refactorings that moved close() earlier in the flow.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- BPMN engine has not been initialized
- Cannot complete case task. Parent process instance
- Cannot complete process instance. Parent process instance
- CaseDiagramGenerator already closed
- CaseDiagramGenerator already closed
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/4e6377657df2ea84.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-image-generator/src/main/java/org/flowable/image/impl/DefaultProcessDiagramCanvas.java:263
ESCALATION_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/escalation.png", customClassLoader));
MESSAGE_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message-throw.png", customClassLoader));
MESSAGE_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/message.png", customClassLoader));
SIGNAL_THROW_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal-throw.png", customClassLoader));
SIGNAL_CATCH_IMAGE = ImageIO.read(ReflectUtil.getResource("org/flowable/icons/signal.png", customClassLoader));
} catch (IOException e) {
LOGGER.warn("Could not load image for process diagram creation: {}", e.getMessage());
}
}
/**
* Generates an image of what currently is drawn on the canvas.
*
* Throws an {@link FlowableImageException} when {@link #close()} is already called.
*/
public InputStream generateImage(String imageType) {
if (closed) {
throw new FlowableImageException("ProcessDiagramGenerator already closed");
}
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
ImageIO.write(processDiagram, imageType, out);
return new ByteArrayInputStream(out.toByteArray());
} catch (IOException e) {
throw new FlowableImageException("Error while generating process image", e);
}
}
/**
* Generates an image of what currently is drawn on the canvas.
*
* Throws an {@link FlowableImageException} when {@link #close()} is already called.
*/
public BufferedImage generateBufferedImage(String imageType) {
if (closed) {
throw new FlowableImageException("ProcessDiagramGenerator already closed");View on GitHub (pinned to d6d39ce1c6)