karatelabs/karate · error · RuntimeException

${formatted template error block (template source…

Error message

${formatted template error block (template source, line/col, cause)}

What it means

When template processing fails for any reason other than a 404 ResourceNotFoundException or an intentional flow-control signal (e.g. context.redirect/context.switch), Markup.process logs the failure via logTemplateError — which formats a block with the template source and line/column of the failure — and throws a RuntimeException whose message is that formatted block and whose cause is the original exception. The message is deliberately self-contained so callers like ServerRequestCycle.handleError can display it in devMode without re-deriving diagnostics.

Solutions

  1. Read the formatted message: it names the template, line/column, and includes the source excerpt — fix the underlying template problem it points to.
  2. Inspect `getCause()` for the original exception (e.g. the processor's RuntimeException) for the true root cause.
  3. If it was an intentional flow control (redirect/switch), it should not reach here — verify your flow-control signal isn't wrapped in a way that hides it from hasFlowControlSignal.
  4. In production mode, rely on logs instead of the response body; keep the cause chain intact when rethrowing.

Example fix

// before (generic handling that hides the formatted block)
} catch (Exception e) {
    log.error("render failed");
}

// after (surface the formatted block and root cause)
} catch (RuntimeException e) {
    log.error("template render failed:\n{}\nroot cause:", e.getMessage(), e.getCause(), e);
    throw e;
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
    markup.process(...);
} catch (RuntimeException e) {
    // e.getMessage() is the formatted template error block; e is the root cause
    log.error("Template render failed:\n{}", e.getMessage(), e);
    if (!devMode) {
        throw new IllegalArgumentException("template render failed (see logs)", e);
    }
    throw e; // devMode callers can print the formatted block to the response
}

Prevention

When it happens

Trigger: Any uncaught exception raised while parsing or executing a template — e.g. a template expression referencing missing variables, a processor throwing (like ka:dispatch or th:each validation errors), or I/O failures — propagates through this wrapper in Markup.process.

Common situations: Dev-mode rendering of a page whose template has a runtime error; seeing this wrapper's message in an HTTP response body during development; diagnosing why a page failed by reading the embedded template source excerpt.

Related errors


AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12). Data as JSON: /api/errors/9aa9f5356528f57a. Report an issue: GitHub.

Appendix: source

Thrown at karate-core/src/main/java/io/karatelabs/markup/Markup.java:119

            TemplateManager templateManager = wrapped.getConfiguration().getTemplateManager();
            templateManager.parseAndProcess(templateSpec, context, writer);
            try {
                writer.flush();
            } catch (IOException e) {
                throw new TemplateOutputException("error flushing output writer", content, -1, -1, e);
            }
        } catch (ResourceNotFoundException e) {
            throw e; // Let 404s bubble up without logging
        } catch (Exception e) {
            if (hasFlowControlSignal(e)) {
                // intentional control flow (e.g. context.redirect, context.switch) — not an error
                throw new RuntimeException(e);
            }
            String formatted = logTemplateError(isPath, content, e);
            // Carry the formatted block as the wrapper's message so callers
            // (e.g. ServerRequestCycle.handleError) can surface it in the
            // response body when in devMode without re-deriving line/col/source.
            throw new RuntimeException(formatted, e);
        }
    }

    private static boolean hasFlowControlSignal(Throwable e) {
        Throwable t = e;
        while (t != null) {
            if (t instanceof FlowControlSignal) {
                return true;
            }
            t = t.getCause();
        }
        return false;
    }

    private static final int CONTEXT_LINES = 2;

    private String logTemplateError(boolean isPath, String template, Exception e) {
        StringBuilder sb = new StringBuilder();

View on GitHub (pinned to a22eb90246)