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

  1. Ensure generateImage()/generateBufferedImage() are called before close() on the canvas
  2. If using try-with-resources, generate the image inside the try block
  3. If the canvas is shared, remove the premature close() or create a new canvas per render
  4. 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

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


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)