This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new ba4901c4c6bb CAMEL-25429: camel-jbang - camel overview lists the error 
handling apart from the flows (#27563)
ba4901c4c6bb is described below

commit ba4901c4c6bb54abab4681edf998fa3675de9706
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 8 15:50:43 2026 +0200

    CAMEL-25429: camel-jbang - camel overview lists the error handling apart 
from the flows (#27563)
    
    camel overview listed a global error handler as a route that calls the
    error route, such as "[email protected]:3 calls parked",
    under Flows between routes. The flows now hold the calls only, and a new
    Error handling section lists each global handler by what it is (dead
    letter channel, onException with its exceptions, route configuration)
    and where it is written, a route's own error handling, the routes the
    failures go to, and the routes with no error handler, whose failures go
    back to the caller. The same split is in the plain text the AI reads.
    
    
    Claude-Session: https://claude.ai/code/session_01STT6whBgK1AqsSsUKrnE8m
    
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../jbang/core/commands/ai/IntegrationSummary.java |  65 +++++++++-
 .../jbang/core/commands/ai/JavaRouteReader.java    |  26 +++-
 .../dsl/jbang/core/commands/ai/ProjectRoutes.java  | 133 +++++++++++++++++----
 .../core/commands/ai/IntegrationSummaryTest.java   |  82 +++++++++++++
 4 files changed, 276 insertions(+), 30 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationSummary.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationSummary.java
index a9ca74ac783e..21cbac6b2aa1 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationSummary.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationSummary.java
@@ -450,9 +450,10 @@ public final class IntegrationSummary {
             sb.append('\n');
         }
 
-        if (!overview.links().isEmpty()) {
+        List<Link> calls = overview.links().stream().filter(l -> 
!isErrorPath(overview, l)).toList();
+        if (!calls.isEmpty()) {
             sb.append("## Flows between routes\n\n");
-            for (Link l : overview.links()) {
+            for (Link l : calls) {
                 sb.append("- `").append(l
                         .from()).append("` 
").append(linkVerb(l.kind())).append(" `").append(l.to())
                         .append("` over `").append(l.endpoint()).append("`\n");
@@ -460,6 +461,14 @@ public final class IntegrationSummary {
             sb.append('\n');
         }
 
+        // how failures travel: not flows, so listed apart, by what handles 
them
+        List<String> errorLines = errorHandlingLines(overview, true);
+        if (!errorLines.isEmpty()) {
+            sb.append("## Error handling\n\n");
+            errorLines.forEach(line -> sb.append("- 
").append(line).append('\n'));
+            sb.append('\n');
+        }
+
         if (!overview.systems().isEmpty()) {
             sb.append("## External systems\n\n");
             Map<String, Map<String, Set<String>[]>> byCategory = new 
TreeMap<>();
@@ -623,6 +632,48 @@ public final class IntegrationSummary {
         };
     }
 
+    /** A link that carries a failure: sent to from an onException, a doCatch 
or a dead letter channel. */
+    static boolean isErrorPath(Overview overview, Link l) {
+        if (l.onError()) {
+            return true;
+        }
+        Route from = overview.route(l.from());
+        return from != null && "errorHandler".equals(from.kind());
+    }
+
+    /**
+     * The error handling of the project, one line per handler: a global one 
(onException, dead letter channel, route
+     * configuration) with where it is written, or a route's own, and the 
routes its failures go to.
+     */
+    static List<String> errorHandlingLines(Overview overview, boolean 
markdown) {
+        Map<String, Set<String>> targets = new LinkedHashMap<>();
+        for (Link l : overview.links()) {
+            if (isErrorPath(overview, l)) {
+                targets.computeIfAbsent(l.from(), k -> new 
LinkedHashSet<>()).add(l.to());
+            }
+        }
+        String q = markdown ? "`" : "";
+        List<String> lines = new ArrayList<>();
+        targets.forEach((from, to) -> {
+            Route r = overview.route(from);
+            String what;
+            if (r != null && "errorHandler".equals(r.kind())) {
+                String label = r.description() != null ? r.description() : 
"error handling";
+                what = label + " (global, " + r.file() + ":" + r.line() + ")";
+            } else {
+                what = q + from + q + " (its own error handling)";
+            }
+            lines.add(what + ": failures to " + String.join(", ", 
to.stream().map(t -> q + t + q).toList()));
+        });
+        // a route with no error handler: the global one does not apply, its 
failures go back to its caller
+        List<String> none = 
overview.flows().stream().filter(Route::noErrorHandler).map(Route::key).toList();
+        if (!none.isEmpty()) {
+            lines.add("no error handler in " + String.join(", ", 
none.stream().map(n -> q + n + q).toList())
+                      + ": failures go back to the caller, whose error 
handling acts");
+        }
+        return lines;
+    }
+
     private static String linkVerb(String kind) {
         return switch (kind) {
             case "call" -> "calls";
@@ -737,13 +788,19 @@ public final class IntegrationSummary {
                         .append(e.route() != null ? " -> " + e.route() : 
"").append('\n');
             }
         }
-        if (!overview.links().isEmpty()) {
+        List<Link> callLinks = overview.links().stream().filter(l -> 
!isErrorPath(overview, l)).toList();
+        if (!callLinks.isEmpty()) {
             sb.append("\nFlows between routes:\n");
-            for (Link l : overview.links()) {
+            for (Link l : callLinks) {
                 sb.append("- ").append(l.from()).append(' 
').append(linkVerb(l.kind())).append(' ').append(l.to())
                         .append('\n');
             }
         }
+        List<String> errorLines = errorHandlingLines(overview, false);
+        if (!errorLines.isEmpty()) {
+            sb.append("\nError handling:\n");
+            errorLines.forEach(line -> sb.append("- 
").append(line).append('\n'));
+        }
         if (!overview.systems().isEmpty()) {
             sb.append("\nExternal systems:\n");
             Set<String> seen = new LinkedHashSet<>();
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/JavaRouteReader.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/JavaRouteReader.java
index d0826e09eae3..e9b5817a45b3 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/JavaRouteReader.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/JavaRouteReader.java
@@ -55,6 +55,7 @@ import org.apache.camel.model.SwitchDefinition;
 import org.apache.camel.model.ToDynamicDefinition;
 import org.apache.camel.model.TryDefinition;
 import org.apache.camel.model.errorhandler.DeadLetterChannelDefinition;
+import org.apache.camel.model.errorhandler.NoErrorHandlerDefinition;
 import org.apache.camel.model.language.ConstantExpression;
 import org.apache.camel.model.language.ExpressionDefinition;
 import org.apache.camel.model.rest.RestDefinition;
@@ -150,10 +151,20 @@ final class JavaRouteReader {
         for (ProcessorDefinition<?> p : r.getOutputs()) {
             decisions(p, scope, decisions, visited, 0);
         }
-        routes.add(new Route(
+        Route route = new Route(
                 id, template != null ? "routeTemplate" : "route", description, 
r.getGroup(), file, Math.max(1, line),
                 "java", partial, from, consumes, produces, 
r.getOutputs().size(), -1, 0, null, null,
-                logOnly(r, produces), note, decisions));
+                logOnly(r, produces), note, decisions);
+        // the route's own error handler, such as 
errorHandler(noErrorHandler())
+        if (r.isErrorHandlerFactorySet()) {
+            ErrorHandlerFactory eh = r.getErrorHandlerFactory();
+            if (eh instanceof NoErrorHandlerDefinition) {
+                route = route.withErrorHandler("noErrorHandler");
+            } else if (eh instanceof DeadLetterChannelDefinition) {
+                route = route.withErrorHandler("deadLetterChannel");
+            }
+        }
+        routes.add(route);
     }
 
     /** The decision points below a step, with their paths (see {@link 
RouteDecisions}). */
@@ -304,10 +315,15 @@ final class JavaRouteReader {
     /** onException and dead letter channels outside a route, as one error 
handler route of the file. */
     private void errorHandlers(JavaParseResult result) {
         List<Endpoint> produces = new ArrayList<>();
+        List<String> labels = new ArrayList<>();
         int line = Integer.MAX_VALUE;
         Set<Object> seen = Collections.newSetFromMap(new IdentityHashMap<>());
         for (OnExceptionDefinition oe : result.routes().getOnExceptions()) {
             walk(oe, true, produces, new ArrayList<>(), seen, 0);
+            labels.add(oe.getExceptions().isEmpty()
+                    ? "onException"
+                    : "onException " + String.join(", ",
+                            
oe.getExceptions().stream().map(ProjectRoutes::simpleName).toList()));
             if (oe.getLineNumber() > 0) {
                 line = Math.min(line, oe.getLineNumber());
             }
@@ -316,13 +332,17 @@ final class JavaRouteReader {
             ErrorHandlerFactory eh = r.getErrorHandlerFactory();
             if (eh instanceof DeadLetterChannelDefinition dlc && 
dlc.getDeadLetterUri() != null) {
                 add(produces, ProjectRoutes.endpoint(dlc.getDeadLetterUri(), 
null, false, catalog).asOnError());
+                if (!labels.contains("dead letter channel")) {
+                    labels.add("dead letter channel");
+                }
                 line = Math.min(line, Math.max(1, r.getLineNumber()));
             }
         }
         if (!produces.isEmpty()) {
             int at = line == Integer.MAX_VALUE ? 1 : line;
             routes.add(new Route(
-                    ProjectRoutes.ERROR_HANDLER_PREFIX + file + ":" + at, 
"errorHandler", null, null, file, at, "java",
+                    ProjectRoutes.ERROR_HANDLER_PREFIX + file + ":" + at, 
"errorHandler",
+                    labels.isEmpty() ? null : String.join("; ", labels), null, 
file, at, "java",
                     false, null, List.of(), 
produces.stream().map(Endpoint::asOnError).toList(), 0, -1, 0, null, null,
                     false, null));
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ProjectRoutes.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ProjectRoutes.java
index 5bd573654a33..3cbab6597102 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ProjectRoutes.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ProjectRoutes.java
@@ -75,6 +75,24 @@ public final class ProjectRoutes {
     private static final Set<String> STEP_CONTAINERS = Set.of("steps", "when", 
"doCatch", "doFinally");
     private static final int MAX_DEPTH = 64;
     private static final int MAX_PATH = 60;
+
+    /** The simple name of a class: IllegalStateException for 
java.lang.IllegalStateException. */
+    static String simpleName(String className) {
+        String n = className.trim();
+        int dot = n.lastIndexOf('.');
+        return dot >= 0 ? n.substring(dot + 1) : n;
+    }
+
+    /** An error handler kind in words: a dead letter channel, the default 
error handler, no error handler. */
+    static String errorHandlerKind(String name) {
+        return switch (name) {
+            case "deadLetterChannel" -> "dead letter channel";
+            case "defaultErrorHandler" -> "default error handler";
+            case "noErrorHandler" -> "no error handler";
+            default -> name;
+        };
+    }
+
     /** The id given to an onException or error handler outside a route, 
followed by where it is written. */
     public static final String ERROR_HANDLER_PREFIX = "error-handler@";
 
@@ -97,29 +115,53 @@ public final class ProjectRoutes {
     /**
      * A route of the project.
      *
-     * @param id        the route id, null when the source gives none
-     * @param kind      route, routeTemplate, templatedRoute, rest, or 
errorHandler (an onException or error handler
-     *                  outside a route, with the endpoints it sends failed 
messages to)
-     * @param file      the file, relative to the project directory
-     * @param line      the 1-based line the route starts at
-     * @param format    yaml, xml or java
-     * @param heuristic whether the route was found by pattern matching (Java 
DSL) and may be incomplete
-     * @param from      what the route consumes from, null for a REST 
operation without a route
-     * @param consumes  every endpoint the route consumes: its from, what it 
polls, and the kamelet name of a template
-     * @param produces  the endpoints the route sends to, in order
-     * @param steps     roughly how many steps the route has
-     * @param insertAt  where a description line can be inserted (YAML), or -1
-     * @param indent    the indentation of that line (YAML)
-     * @param restVerb  the HTTP verb of a REST operation
-     * @param restPath  the path of a REST operation
-     * @param logOnly   whether every step of the route only logs: plumbing, 
not business logic
-     * @param note      the route's note: a longer explanation beside the 
short description, may be null
-     * @param decisions the decision points of the route (choice, filter, 
split, ...), in route order
+     * @param id           the route id, null when the source gives none
+     * @param kind         route, routeTemplate, templatedRoute, rest, or 
errorHandler (an onException or error handler
+     *                     outside a route, with the endpoints it sends failed 
messages to)
+     * @param file         the file, relative to the project directory
+     * @param line         the 1-based line the route starts at
+     * @param format       yaml, xml or java
+     * @param heuristic    whether the route was found by pattern matching 
(Java DSL) and may be incomplete
+     * @param from         what the route consumes from, null for a REST 
operation without a route
+     * @param consumes     every endpoint the route consumes: its from, what 
it polls, and the kamelet name of a
+     *                     template
+     * @param produces     the endpoints the route sends to, in order
+     * @param steps        roughly how many steps the route has
+     * @param insertAt     where a description line can be inserted (YAML), or 
-1
+     * @param indent       the indentation of that line (YAML)
+     * @param restVerb     the HTTP verb of a REST operation
+     * @param restPath     the path of a REST operation
+     * @param logOnly      whether every step of the route only logs: 
plumbing, not business logic
+     * @param note         the route's note: a longer explanation beside the 
short description, may be null
+     * @param decisions    the decision points of the route (choice, filter, 
split, ...), in route order
+     * @param errorHandler the route's own error handler (deadLetterChannel, 
defaultErrorHandler, noErrorHandler, or a
+     *                     reference), null when the route uses the global one
      */
     public record Route(String id, String kind, String description, String 
group, String file, int line, String format,
             boolean heuristic, Endpoint from, List<Endpoint> consumes, 
List<Endpoint> produces, int steps,
             int insertAt, int indent, String restVerb, String restPath, 
boolean logOnly, String note,
-            List<RouteDecisions.DecisionPoint> decisions) {
+            List<RouteDecisions.DecisionPoint> decisions, String errorHandler) 
{
+
+        /** A route that uses the global error handler. */
+        public Route(String id, String kind, String description, String group, 
String file, int line, String format,
+                     boolean heuristic, Endpoint from, List<Endpoint> 
consumes, List<Endpoint> produces, int steps,
+                     int insertAt, int indent, String restVerb, String 
restPath, boolean logOnly, String note,
+                     List<RouteDecisions.DecisionPoint> decisions) {
+            this(id, kind, description, group, file, line, format, heuristic, 
from, consumes, produces, steps, insertAt,
+                 indent, restVerb, restPath, logOnly, note, decisions, null);
+        }
+
+        /** The same route with its own error handler. */
+        public Route withErrorHandler(String errorHandler) {
+            return new Route(
+                    id, kind, description, group, file, line, format, 
heuristic, from, consumes, produces, steps,
+                    insertAt, indent, restVerb, restPath, logOnly, note, 
decisions, errorHandler);
+        }
+
+        /** Whether the route has no error handler: its failures go back to 
its caller. */
+        public boolean noErrorHandler() {
+            return "noErrorHandler".equals(errorHandler);
+        }
 
         /** A route without decision points. */
         public Route(String id, String kind, String description, String group, 
String file, int line, String format,
@@ -429,7 +471,8 @@ public final class ProjectRoutes {
                         rest(m, line);
                     }
                 }
-                case "onException", "errorHandler", "routeConfiguration", 
"route-configuration" -> errorHandler(value, line);
+                case "onException", "errorHandler", "routeConfiguration", 
"route-configuration" ->
+                    errorHandler(key, value, line);
                 default -> {
                     // beans, restConfiguration and friends are not routes
                 }
@@ -482,6 +525,10 @@ public final class ProjectRoutes {
                     false, from, consumes, produces, steps[0], insertAt, 
indent, null, null,
                     logOnly(fromNode instanceof MappingNode fm2 ? child(fm2, 
"steps") : child(m, "steps")),
                     str(value(m, "note")), decisions);
+            // the route's own error handler, such as noErrorHandler
+            if (child(m, "errorHandler") instanceof MappingNode em && 
!em.getValue().isEmpty()) {
+                r = 
r.withErrorHandler(scalar(em.getValue().get(0).getKeyNode()));
+            }
             routes.add(r);
             return r;
         }
@@ -555,16 +602,45 @@ public final class ProjectRoutes {
         }
 
         /** Where an onException or error handler sends failed messages, as a 
route that only error handling uses. */
-        private void errorHandler(org.yaml.snakeyaml.nodes.Node value, int 
line) {
+        private void errorHandler(String key, org.yaml.snakeyaml.nodes.Node 
value, int line) {
             List<Endpoint> produces = new ArrayList<>();
             walk(value, produces, new ArrayList<>(), new int[] { 0 }, 0, true);
             if (!produces.isEmpty()) {
                 routes.add(new Route(
-                        ERROR_HANDLER_PREFIX + file + ":" + line, 
"errorHandler", null, null, file, line, "yaml",
+                        ERROR_HANDLER_PREFIX + file + ":" + line, 
"errorHandler", errorHandlerLabel(key, value), null,
+                        file, line, "yaml",
                         false, null, List.of(), produces, 0, -1, 0, null, 
null, false, null));
             }
         }
 
+        /**
+         * What a global error handler is, in words: onException with its 
exceptions, a dead letter channel, or a route
+         * configuration.
+         */
+        private static String errorHandlerLabel(String key, 
org.yaml.snakeyaml.nodes.Node value) {
+            if ("onException".equals(key)) {
+                List<String> exceptions = new ArrayList<>();
+                if (value instanceof MappingNode m) {
+                    for (NodeTuple t : m.getValue()) {
+                        if (t.getKeyNode() instanceof ScalarNode k && 
"exception".equals(k.getValue())
+                                && t.getValueNode() instanceof SequenceNode 
seq) {
+                            for (org.yaml.snakeyaml.nodes.Node n : 
seq.getValue()) {
+                                if (n instanceof ScalarNode sn) {
+                                    exceptions.add(simpleName(sn.getValue()));
+                                }
+                            }
+                        }
+                    }
+                }
+                return exceptions.isEmpty() ? "onException" : "onException " + 
String.join(", ", exceptions);
+            }
+            if ("errorHandler".equals(key) && value instanceof MappingNode m 
&& !m.getValue().isEmpty()
+                    && m.getValue().get(0).getKeyNode() instanceof ScalarNode 
k) {
+                return errorHandlerKind(k.getValue());
+            }
+            return "route configuration";
+        }
+
         /** Whether every top-level step only logs: a log EIP, or a to or 
wireTap of a log endpoint. */
         private boolean logOnly(org.yaml.snakeyaml.nodes.Node steps) {
             if (!(steps instanceof SequenceNode seq) || 
seq.getValue().isEmpty()) {
@@ -935,6 +1011,17 @@ public final class ProjectRoutes {
 
         private void errorHandler(Element e) {
             List<Endpoint> produces = new ArrayList<>();
+            String label;
+            if ("onException".equals(localName(e))) {
+                List<String> exceptions = new ArrayList<>();
+                NodeList ex = e.getElementsByTagNameNS("*", "exception");
+                for (int i = 0; i < ex.getLength(); i++) {
+                    exceptions.add(simpleName(ex.item(i).getTextContent()));
+                }
+                label = exceptions.isEmpty() ? "onException" : "onException " 
+ String.join(", ", exceptions);
+            } else {
+                label = attr(e, "deadLetterUri") != null ? "dead letter 
channel" : "error handler";
+            }
             if (attr(e, "deadLetterUri") != null) {
                 add(produces, endpoint(attr(e, "deadLetterUri"), null, false, 
catalog));
             }
@@ -950,7 +1037,7 @@ public final class ProjectRoutes {
             if (!produces.isEmpty()) {
                 int line = lineOf(localName(e), null);
                 routes.add(new Route(
-                        ERROR_HANDLER_PREFIX + file + ":" + line, 
"errorHandler", null, null, file, line, "xml", false,
+                        ERROR_HANDLER_PREFIX + file + ":" + line, 
"errorHandler", label, null, file, line, "xml", false,
                         null, List.of(), 
produces.stream().map(Endpoint::asOnError).toList(), 0, -1, 0, null, null,
                         false, null));
             }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationSummaryTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationSummaryTest.java
index ce427be76644..0bcb9f6bc50b 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationSummaryTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/IntegrationSummaryTest.java
@@ -188,6 +188,88 @@ class IntegrationSummaryTest {
         assertThat(back.descriptions()).isEqualTo(ai.descriptions());
     }
 
+    @Test
+    void errorHandlingIsNotAFlow() {
+        // fail-well/error-handling: a global dead letter channel and 
onException to parked, and payment-provider with
+        // no error handler
+        String yaml = """
+                - errorHandler:
+                    deadLetterChannel:
+                      deadLetterUri: direct:parked
+                - onException:
+                    exception:
+                      - java.lang.IllegalStateException
+                    handled:
+                      constant: "true"
+                    steps:
+                      - to:
+                          uri: direct:parked
+                - route:
+                    id: checkout
+                    from:
+                      uri: file:orders
+                      steps:
+                        - to:
+                            uri: direct:charge
+                - route:
+                    id: payment-provider
+                    errorHandler:
+                      noErrorHandler: {}
+                    from:
+                      uri: direct:charge
+                      steps:
+                        - log:
+                            message: charging
+                - route:
+                    id: parked
+                    from:
+                      uri: direct:parked
+                      steps:
+                        - to:
+                            uri: file:parked
+                """;
+        Overview o = ProjectOverview.analyze(
+                Path.of("shop"), Map.of("error-handling.camel.yaml", yaml), 
ProjectOverviewTest.CATALOG);
+        String md = IntegrationSummary.render(o, null, null, null, 
"2026-10-08");
+
+        String flows = md.substring(md.indexOf("## Flows between routes"), 
md.indexOf("## Error handling"));
+        assertThat(flows).contains("`checkout` calls 
`payment-provider`").doesNotContain("parked")
+                .doesNotContain("error-handler@");
+        assertThat(md).contains("## Error handling")
+                .contains("- dead letter channel (global, 
error-handling.camel.yaml:1): failures to `parked`")
+                .contains("- onException IllegalStateException (global, 
error-handling.camel.yaml:4): failures to `parked`")
+                .contains("- no error handler in `payment-provider`: failures 
go back to the caller");
+    }
+
+    @Test
+    void errorHandlingIsNotAFlowInJava() {
+        String java = """
+                import org.apache.camel.builder.RouteBuilder;
+
+                public class Shop extends RouteBuilder {
+                    public void configure() {
+                        
onException(IllegalStateException.class).handled(true).to("direct:parked");
+
+                        from("file:orders").routeId("checkout")
+                            .to("direct:charge");
+
+                        
from("direct:charge").routeId("payment-provider").errorHandler(noErrorHandler())
+                            .log("charging");
+
+                        from("direct:parked").routeId("parked")
+                            .to("file:parked");
+                    }
+                }
+                """;
+        Overview o = ProjectOverview.analyze(
+                Path.of("shop"), Map.of("src/main/java/Shop.java", java), 
ProjectOverviewTest.CATALOG);
+        String md = IntegrationSummary.render(o, null, null, null, 
"2026-10-08");
+
+        assertThat(md).contains("`checkout` calls `payment-provider`")
+                .contains("- onException IllegalStateException (global, 
src/main/java/Shop.java:")
+                .contains("- no error handler in `payment-provider`: failures 
go back to the caller");
+    }
+
     @Test
     void factsOnlySummary() {
         Overview o = ProjectOverviewTest.overview();

Reply via email to