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 9fa6919de995 CAMEL-25565: camel-jbang - route-diagram, route-structure 
and route-topology read Java source files with the Java DSL parser instead of 
compiling them (#27715)
9fa6919de995 is described below

commit 9fa6919de9956050f26338d97544e0e6f249f783
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Oct 15 21:50:33 2026 +0200

    CAMEL-25565: camel-jbang - route-diagram, route-structure and 
route-topology read Java source files with the Java DSL parser instead of 
compiling them (#27715)
    
    Given Java source files, the three commands (and the 
camel_render_route_diagram
    MCP tool) booted a short-lived Camel to compile the routes and dump the 
model,
    which took seconds and needed the project's classes and dependencies. They 
now
    read Java sources with the Java DSL parser, as camel transform route and 
camel
    overview already do: the routes are added to a CamelContext that is never
    started and dumped with the same DefaultDumpRoutesStrategy. Parts the parser
    cannot read are listed (as an unread array with --json). Routes and steps 
get
    their ids as when Camel starts, so a route without routeId is shown. A new
    --compile option restores the old behaviour. YAML and XML sources are 
unchanged.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
    Signed-off-by: Claus Ibsen <[email protected]>
---
 design/java-dsl-parser.adoc                        |   5 +
 .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc    |   9 +
 .../ROOT/pages/camel-jbang-diagnostics.adoc        |   3 +-
 .../camel-jbang-cmd-route-diagram.adoc             |   1 +
 .../camel-jbang-cmd-route-structure.adoc           |   1 +
 .../camel-jbang-cmd-route-topology.adoc            |   1 +
 .../modules/ROOT/pages/route-diagram.adoc          |   5 +
 .../META-INF/camel-jbang-commands-metadata.json    |   2 +-
 .../jbang/core/commands/TransformJavaRoutes.java   | 114 +++++++++--
 .../core/commands/action/ActionBaseCommand.java    |  67 ++++++-
 .../commands/action/CamelRouteDiagramAction.java   |  14 +-
 .../commands/action/CamelRouteStructureAction.java |  14 ++
 .../commands/action/CamelRouteTopologyAction.java  |  12 +-
 .../commands/action/RouteActionJavaSourceTest.java | 210 +++++++++++++++++++++
 .../jbang/core/commands/mcp/RouteDiagramTools.java |  16 +-
 15 files changed, 442 insertions(+), 32 deletions(-)

diff --git a/design/java-dsl-parser.adoc b/design/java-dsl-parser.adoc
index 80ee524bbfd2..8c021b46e5ac 100644
--- a/design/java-dsl-parser.adoc
+++ b/design/java-dsl-parser.adoc
@@ -108,6 +108,11 @@ camel-jbang reads Java routes with the parser only; the 
pattern matching on stri
 running Camel, in a CamelContext that is never started (CAMEL-25202); it 
compiles the routes only when one cannot be
 read completely, or with `--compile`.
 
+`camel cmd route-diagram`, `route-topology` and `route-structure` (and the 
`camel_render_route_diagram` MCP tool) read
+Java source files the same way, and dump the route structure and topology JSON 
from that CamelContext (CAMEL-25565). A
+route read only partly is still shown: a lambda or a processor is the 
placeholder step, and the parts the parser could
+not read are listed. They compile the routes only with `--compile`.
+
 == Security
 
 The parser reads sources nobody vetted: a cloned example opened in the TUI, 
documentation, a project an AI agent
diff --git 
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 1f85081cbd9a..23ffb50c014e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -841,6 +841,15 @@ failures of the route go. A highlighted path that steps 
into an `onException` cl
 The `<camel-topology-diagram>` web component no longer draws the error paths 
of the route topology as calls between
 routes. With its new `errors` attribute it draws the routes reached only on 
error in a frame below the other routes.
 
+=== camel-jbang - route-diagram, route-structure and route-topology read Java 
sources without compiling them
+
+Given Java source files, `camel cmd route-diagram`, `camel cmd 
route-structure` and `camel cmd route-topology` (and the
+`camel_render_route_diagram` MCP tool) now read the routes with the Java DSL 
parser, as `camel transform route` and
+`camel overview` do, instead of compiling the sources and loading their routes 
in a short-lived Camel. A step the parser
+cannot work out, such as a lambda or a processor, is shown as a placeholder, 
and the parts it could not read are
+listed (with `--json`, as an `unread` array in the JSON). Use the new 
`--compile` option to compile the sources and
+load their routes as before.
+
 === camel-jbang - export to Quarkus keeps the names in `quarkus.*` keys
 
 `camel export --runtime=quarkus` wrote every `quarkus.*` key except 
`quarkus.hawtio.*` in dash case: an upper-case
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-diagnostics.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-diagnostics.adoc
index 8ed30eb3de6b..1613e5130bb7 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-diagnostics.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-diagnostics.adoc
@@ -180,7 +180,8 @@ camel cmd route-structure myApp --filter=myRoute --brief
 camel cmd route-structure myApp --description
 ----
 
-Give a source file instead of a running integration to see the structure 
without running it.
+Give a source file instead of a running integration to see the structure 
without running it. A Java source file is read
+by the Java DSL parser, without compiling it; use `--compile` to compile it 
instead.
 `--brief` shortens each node, and `--description` shows the description of 
each node instead of its code.
 
 === Route dump
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-diagram.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-diagram.adoc
index 960f11cdfe34..a1014d6a478c 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-diagram.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-diagram.adoc
@@ -20,6 +20,7 @@ camel cmd route-diagram [options]
 |===
 | Option | Description | Default | Type
 | `--box-width` | Node box width in logical pixels | 180 | int
+| `--compile` | Whether to compile Java source files and load their routes. By 
default Java routes are read by the Java DSL parser, without compiling them | 
false | boolean
 | `--errors` | Draw the error handling of each route in a frame below its 
steps: its onException clauses, where its failures go and where it is reached 
on error from. Without it only the steps of the route are drawn |  | boolean
 | `--filter` | Filter route by filename or route id |  | String
 | `--font-size` | Font size in logical pixels for node text | 12 | int
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-structure.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-structure.adoc
index 8ce1fdceb2b5..187e045a7618 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-structure.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-structure.adoc
@@ -20,6 +20,7 @@ camel cmd route-structure [options]
 |===
 | Option | Description | Default | Type
 | `--brief` | To show less detailed route structure |  | boolean
+| `--compile` | Whether to compile Java source files and load their routes. By 
default Java routes are read by the Java DSL parser, without compiling them | 
false | boolean
 | `--description` | To show description instead of code |  | boolean
 | `--filter` | Filter route by filename or route id (multiple names can be 
separated by comma) |  | String
 | `--ignore-loading-error` | Whether to ignore route loading and compilation 
errors (use this with care!) | false | boolean
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-topology.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-topology.adoc
index 53a58591d1f0..190a9cd947b2 100644
--- 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-topology.adoc
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-route-topology.adoc
@@ -20,6 +20,7 @@ camel cmd route-topology [options]
 |===
 | Option | Description | Default | Type
 | `--box-width` | Width of diagram node boxes | 180 | int
+| `--compile` | Whether to compile Java source files and load their routes. By 
default Java routes are read by the Java DSL parser, without compiling them | 
false | boolean
 | `--description` | Prefer route description over route id in node labels |  | 
boolean
 | `--errors` | Draw the error handling: the routes reached only on error in a 
frame of their own below the routes, with where their failures come from (with 
--theme) |  | boolean
 | `--external` | Include external systems (consumers at top, producers at 
bottom) |  | boolean
diff --git a/docs/user-manual/modules/ROOT/pages/route-diagram.adoc 
b/docs/user-manual/modules/ROOT/pages/route-diagram.adoc
index bca05e6582bd..b461d2b20438 100644
--- a/docs/user-manual/modules/ROOT/pages/route-diagram.adoc
+++ b/docs/user-manual/modules/ROOT/pages/route-diagram.adoc
@@ -27,6 +27,11 @@ It also works with Java source files:
 camel cmd route-diagram MyRoute.java
 ----
 
+Java routes are read by the Java DSL parser, without compiling them, so the 
diagram is ready in milliseconds and the
+source does not need its dependencies. A step the parser cannot work out, such 
as a lambda or a processor, is shown as a
+placeholder, and the parts it could not read are listed. Use `--compile` to 
compile the source and load its routes
+instead. The same applies to `camel cmd route-topology` and `camel cmd 
route-structure`.
+
 And you can include multiple files:
 
 [source,bash]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index 55f88b0342ce..5355a0ee5470 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -3,7 +3,7 @@
     { "name": "ask", "fullName": "ask", "description": "Ask a question about a 
running Camel application using AI", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Ask", "options": [ { "names": 
"--api-key", "description": "API key. Also reads ANTHROPIC_API_KEY, 
OPENAI_API_KEY, WATSONX_APIKEY, or LLM_API_KEY env vars", "javaType": 
"java.lang.String", "type": "string" }, { "names": "--api-type", "description": 
"API type: 'ollama', 'openai', 'anthropic', or 'watsonx'", "javaType":  [...]
     { "name": "bind", "fullName": "bind", "description": "DEPRECATED: Bind 
source and sink Kamelets as a new Camel integration", "deprecated": true, 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.bind.Bind", "options": 
[ { "names": "--error-handler", "description": "Add error handler 
(none|log|sink:<endpoint>). Sink endpoints are expected in the format 
[[apigroup\/]version:]kind:[namespace\/]name, plain Camel URIs or Kamelet 
name.", "javaType": "java.lang.String", "type": "stri [...]
     { "name": "catalog", "fullName": "catalog", "description": "List artifacts 
from Camel Catalog", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogCommand", "options": [ 
{ "names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"component", "fullName": "catalog component", "description": "List components 
from the Camel Catalog", "sourceClass": "org.apache.camel.dsl.jbang.co [...]
-    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
+    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
     { "name": "completion", "fullName": "completion", "description": "Generate 
completion script for bash\/zsh", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ] },
     { "name": "config", "fullName": "config", "description": "Get and set user 
configuration values", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", 
"fullName": "config get", "description": "Display user configuration value", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
     { "name": "debug", "fullName": "debug", "description": "Debug local Camel 
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug", 
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd 
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background", "description": "Run in the background", "defaultValue": 
"false", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background-wait", "description": "To  [...]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformJavaRoutes.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformJavaRoutes.java
index 1172cf28159d..331a9fc698c4 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformJavaRoutes.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/TransformJavaRoutes.java
@@ -27,32 +27,44 @@ import java.util.Map;
 import java.util.function.Supplier;
 import java.util.stream.Stream;
 
+import org.apache.camel.NamedNode;
 import org.apache.camel.catalog.CamelCatalog;
 import org.apache.camel.catalog.DefaultCamelCatalog;
 import org.apache.camel.dsl.jbang.core.commands.ai.ProjectRoutes;
 import org.apache.camel.impl.DefaultCamelContext;
 import org.apache.camel.impl.DefaultDumpRoutesStrategy;
 import org.apache.camel.java.in.JavaParseResult;
+import org.apache.camel.model.RouteDefinition;
+import org.apache.camel.model.RouteDefinitionHelper;
 import org.apache.camel.spi.Resource;
 import org.apache.camel.support.ResourceHelper;
 
 /**
- * {@code camel transform route} for Java routes without compiling them 
(CAMEL-25202): the sources are read into the
- * model by the Java DSL parser of camel-java-io, and dumped as YAML or XML by 
the same dump strategy a running Camel
- * uses ({@code camel.main.dumpRoutes}), in a CamelContext that is never 
started. Nothing of the project is compiled,
- * loaded or run.
+ * Java routes read without compiling them: the sources are read into the 
model by the Java DSL parser of camel-java-io,
+ * and dumped by the same dump strategy a running Camel uses ({@code 
camel.main.dumpRoutes}), in a CamelContext that is
+ * never started. Nothing of the project is compiled, loaded or run.
+ * <p>
+ * Used by {@code camel transform route} (as YAML or XML, CAMEL-25202), and by 
{@code camel cmd route-diagram},
+ * {@code route-structure} and {@code route-topology} (as the route structure 
and topology JSON, CAMEL-25565).
  */
-final class TransformJavaRoutes {
+public final class TransformJavaRoutes {
 
-    /** Whether the routes were transformed, and why not: the first part the 
parser could not read. */
-    record Result(boolean transformed, String reason) {
+    /**
+     * Whether the routes were dumped, and why not; and the parts the parser 
could not read (empty when every route was
+     * read completely).
+     */
+    public record Result(boolean transformed, String reason, List<String> 
unread) {
+    }
+
+    /** The routes read from the files, and the parts of them the parser could 
not read. */
+    private record Read(List<JavaParseResult> results, List<Resource> 
resources, List<String> unread, String failure) {
     }
 
     private TransformJavaRoutes() {
     }
 
     /** Whether the files are all Java sources, which the parser may read. */
-    static boolean applies(List<String> files) {
+    public static boolean applies(List<String> files) {
         return !files.isEmpty()
                 && files.stream().allMatch(f -> f.endsWith(".java") && 
Files.isRegularFile(Path.of(f)));
     }
@@ -67,33 +79,86 @@ final class TransformJavaRoutes {
      */
     static Result transform(List<String> files, String format, String output, 
boolean uriAsParameters)
             throws Exception {
+        Read read = read(files, true);
+        if (read.failure() != null) {
+            return new Result(false, read.failure(), read.unread());
+        }
+        dump(read, format, output, uriAsParameters);
+        return new Result(true, null, List.of());
+    }
+
+    /**
+     * Dumps the route structure and the route topology of the Java routes of 
the files as JSON, as
+     * {@code camel.main.dumpRoutes=json} does: one route structure file per 
source file, and
+     * {@code route-topology.json}. A route the parser reads only partly is 
dumped too: a step it cannot work out (a
+     * lambda, a processor) is the placeholder the parser puts in the model, 
and the parts it could not read are
+     * returned.
+     *
+     * @param  output the directory (or file) to write to
+     * @return        whether they were dumped; else why not (no file has a 
route the parser can read)
+     */
+    public static Result dumpStructure(List<String> files, String output) 
throws Exception {
+        Read read = read(files, false);
+        if (read.failure() != null) {
+            return new Result(false, read.failure(), read.unread());
+        }
+        dump(read, "json", output, false);
+        return new Result(true, null, read.unread());
+    }
+
+    /**
+     * Reads the routes of the files.
+     *
+     * @param complete whether every route must be read completely: else the 
first part the parser could not read is the
+     *                 failure; when not, a file without routes is skipped, 
and the failure is that no file has one
+     */
+    private static Read read(List<String> files, boolean complete) throws 
Exception {
         CamelCatalog catalog = new DefaultCamelCatalog();
         Map<String, Supplier<String>> sources = javaSources(files);
         List<JavaParseResult> results = new ArrayList<>();
         List<Resource> resources = new ArrayList<>();
+        List<String> unread = new ArrayList<>();
         for (String f : files) {
             String content = Files.readString(Path.of(f), 
StandardCharsets.UTF_8);
             JavaParseResult result = ProjectRoutes.parseJava(content, sources, 
catalog);
             for (JavaParseResult.Unresolved u : result.unresolved()) {
                 if (!JavaParseResult.configuresTheContext(u)) {
-                    return new Result(false, f + ":" + u.line() + " " + 
u.text() + " (" + u.reason() + ")");
+                    String part = f + ":" + u.line() + " " + u.text() + " (" + 
u.reason() + ")";
+                    if (complete) {
+                        return new Read(List.of(), List.of(), List.of(part), 
part);
+                    }
+                    unread.add(part);
                 }
             }
             if (result.routes().getRoutes().isEmpty() && 
result.rests().getRests().isEmpty()
                     && result.routeTemplates().getRouteTemplates().isEmpty()
                     && 
result.routeConfigurations().getRouteConfigurations().isEmpty()) {
-                return new Result(false, f + " has no routes the parser can 
read");
+                if (complete) {
+                    return new Read(List.of(), List.of(), List.of(), f + " has 
no routes the parser can read");
+                }
+                continue;
             }
             results.add(result);
             // the source file, so each is dumped on its own and named after 
it, as when the routes run
             resources.add(ResourceHelper.fromString("file:" + 
Path.of(f).getFileName(), content));
         }
+        if (results.isEmpty()) {
+            return new Read(List.of(), List.of(), unread, String.join(", ", 
files) + " has no routes the parser can read");
+        }
+        return new Read(results, resources, unread, null);
+    }
+
+    private static void dump(Read read, String format, String output, boolean 
uriAsParameters) throws Exception {
         try (DefaultCamelContext context = new DefaultCamelContext()) {
             // never started: adding the definitions only registers them
-            for (int i = 0; i < results.size(); i++) {
-                JavaParseResult r = results.get(i);
-                Resource resource = resources.get(i);
+            for (int i = 0; i < read.results().size(); i++) {
+                JavaParseResult r = read.results().get(i);
+                Resource resource = read.resources().get(i);
                 r.routes().getRoutes().forEach(d -> d.setResource(resource));
+                if ("json".equals(format)) {
+                    // the source of each step, as a routes loader records it 
(the route structure shows it)
+                    r.routes().getRoutes().forEach(d -> locate(d, 
resource.getLocation()));
+                }
                 r.rests().getRests().forEach(d -> d.setResource(resource));
                 r.routeTemplates().getRouteTemplates().forEach(d -> 
d.setResource(resource));
                 r.routeConfigurations().getRouteConfigurations().forEach(d -> 
d.setResource(resource));
@@ -102,6 +167,14 @@ final class TransformJavaRoutes {
                 
context.addRouteTemplateDefinitions(r.routeTemplates().getRouteTemplates());
                 context.addRouteDefinitions(r.routes().getRoutes());
             }
+            if ("json".equals(format)) {
+                // the ids a start assigns: the route structure looks each 
route up by its id, and shows the step ids
+                // (a route id that is a property placeholder is kept as it 
is, as there are no properties)
+                
context.getPropertiesComponent().setIgnoreMissingProperty(true);
+                List<RouteDefinition> routes = context.getRouteDefinitions();
+                RouteDefinitionHelper.forceAssignIds(context, routes);
+                routes.forEach(route -> 
RouteDefinitionHelper.forceAssignIds(context, route));
+            }
             DefaultDumpRoutesStrategy dumper = new DefaultDumpRoutesStrategy();
             dumper.setCamelContext(context);
             
dumper.setInclude("routes,rests,routeConfigurations,beans,dataFormats");
@@ -111,7 +184,20 @@ final class TransformJavaRoutes {
             dumper.setOutput(output);
             dumper.dumpRoutes(format);
         }
-        return new Result(true, null);
+    }
+
+    /** Sets the source location of the node and its children (the line 
numbers the parser has set already). */
+    private static void locate(NamedNode node, String location) {
+        if (node.getLocation() == null) {
+            node.setLocation(location);
+        }
+        if (node instanceof RouteDefinition route && route.getInput() != null 
&& route.getInput().getLocation() == null) {
+            route.getInput().setLocation(location);
+        }
+        List<? extends NamedNode> children = node.getChildren();
+        if (children != null) {
+            children.forEach(c -> locate(c, location));
+        }
     }
 
     /** The Java sources of the files' folders, for the constants a route 
takes from another class. */
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionBaseCommand.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionBaseCommand.java
index cf9bcea3b3c0..029e2982f3c6 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionBaseCommand.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/ActionBaseCommand.java
@@ -28,6 +28,7 @@ import java.util.regex.Pattern;
 import org.apache.camel.dsl.jbang.core.commands.CamelCommand;
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
 import org.apache.camel.dsl.jbang.core.commands.Run;
+import org.apache.camel.dsl.jbang.core.commands.TransformJavaRoutes;
 import org.apache.camel.dsl.jbang.core.common.CamelJBangConstants;
 import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
 import org.apache.camel.dsl.jbang.core.common.PathUtils;
@@ -36,11 +37,15 @@ import org.apache.camel.main.KameletMain;
 import org.apache.camel.support.PatternHelper;
 import org.apache.camel.util.FileUtil;
 import org.apache.camel.util.StopWatch;
+import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 import org.apache.camel.util.json.Jsoner;
 
 abstract class ActionBaseCommand extends CamelCommand {
 
+    /** The parts of Java sources the parser could not read, when the routes 
were read from Java sources. */
+    private List<String> unreadSourceParts;
+
     protected ActionBaseCommand(CamelJBangMain main) {
         super(main);
     }
@@ -189,18 +194,22 @@ abstract class ActionBaseCommand extends CamelCommand {
     }
 
     /**
-     * Boots the given source files through a transient Camel run (see {@link 
Run#runTransform(boolean)}) that dumps the
-     * route structure as JSON into a temporary work directory, so 
route-diagram / route-topology can render from source
-     * files without a running integration. The caller reads the dumped JSON 
from {@link SourceDump#workDir()} and is
-     * responsible for deleting that directory when done.
+     * Dumps the route structure of the given source files as JSON into a 
temporary work directory, so route-diagram /
+     * route-topology can render from source files without a running 
integration. Java sources are read by the Java DSL
+     * parser (see {@link TransformJavaRoutes#dumpStructure(List, String)}) 
without compiling them, unless
+     * {@code compile} is set; other sources, or Java sources with {@code 
compile}, are booted through a transient Camel
+     * run (see {@link Run#runTransform(boolean)}). The caller reads the 
dumped JSON from {@link SourceDump#workDir()}
+     * and is responsible for deleting that directory when done.
      *
      * @param  sourceFiles        the source files to load
      * @param  workDirPrefix      prefix for the (pid-suffixed) temporary work 
directory name
      * @param  ignoreLoadingError whether to ignore route loading and 
compilation errors
-     * @return                    the dump location and the instant the boot 
started, or {@code null} if a file does not
-     *                            exist or the boot failed (an error is 
already printed to the user in that case)
+     * @param  compile            whether to compile Java sources and boot 
them, instead of reading them with the parser
+     * @return                    the dump location and the instant the dump 
started, or {@code null} if a file does not
+     *                            exist or the dump failed (an error is 
already printed to the user in that case)
      */
-    protected SourceDump dumpRoutesFromSource(List<String> sourceFiles, String 
workDirPrefix, boolean ignoreLoadingError)
+    protected SourceDump dumpRoutesFromSource(
+            List<String> sourceFiles, String workDirPrefix, boolean 
ignoreLoadingError, boolean compile)
             throws Exception {
         for (String name : sourceFiles) {
             File f = new File(name);
@@ -217,6 +226,10 @@ abstract class ActionBaseCommand extends CamelCommand {
         // previous --watch iteration whose cleanup above raced with (or lost 
against) a slow filesystem
         Instant bootStart = Instant.now();
 
+        if (!compile && TransformJavaRoutes.applies(sourceFiles)) {
+            return dumpJavaRoutes(sourceFiles, target) ? new 
SourceDump(workDir, bootStart) : null;
+        }
+
         Run run = new Run(getMain()) {
             @Override
             protected void doAddInitialProperty(KameletMain main) {
@@ -242,6 +255,46 @@ abstract class ActionBaseCommand extends CamelCommand {
         return new SourceDump(workDir, bootStart);
     }
 
+    /**
+     * Dumps the route structure and topology JSON of Java sources read by the 
Java DSL parser, and prints what the
+     * parser could not read.
+     *
+     * @return whether the routes were dumped (else an error is already 
printed)
+     */
+    protected boolean dumpJavaRoutes(List<String> sourceFiles, String target) 
throws Exception {
+        TransformJavaRoutes.Result result = 
TransformJavaRoutes.dumpStructure(sourceFiles, target);
+        if (!result.transformed()) {
+            printer().printErr("No routes found: " + result.reason()
+                               + " (use --compile to compile the source and 
load its routes)");
+            return false;
+        }
+        unreadSourceParts = result.unread();
+        if (!unreadSourceParts.isEmpty() && printSourceNotes()) {
+            printer().println("Routes read without compiling them; the parser 
could not work out these parts "
+                              + "(use --compile to compile the source 
instead):");
+            unreadSourceParts.forEach(u -> printer().println("  " + u));
+        }
+        return true;
+    }
+
+    /**
+     * Whether to print the parts of Java sources the parser could not read: 
not when the output is JSON, which lists
+     * them instead (see {@link #addUnreadSourceParts(JsonObject)}).
+     */
+    protected boolean printSourceNotes() {
+        return true;
+    }
+
+    /**
+     * Adds the parts of Java sources the parser could not read to the JSON 
output, as an {@code unread} array, so a
+     * tool reading the JSON can tell that routes may be missing from it.
+     */
+    protected void addUnreadSourceParts(JsonObject jo) {
+        if (unreadSourceParts != null && !unreadSourceParts.isEmpty()) {
+            jo.put("unread", new JsonArray(unreadSourceParts));
+        }
+    }
+
     /**
      * Result of {@link #dumpRoutesFromSource}: the temporary directory the 
route JSON was dumped into, and the instant
      * the transient boot started (used to tell a fresh dump apart from a 
stale one left by an earlier run).
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramAction.java
index 161e8e8e89ae..c9e4eb4416ca 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramAction.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteDiagramAction.java
@@ -130,6 +130,11 @@ public class CamelRouteDiagramAction extends 
ActionWatchCommand {
                         description = "Whether to ignore route loading and 
compilation errors (use this with care!)")
     boolean ignoreLoadingError;
 
+    @CommandLine.Option(names = { "--compile" }, defaultValue = "false",
+                        description = "Whether to compile Java source files 
and load their routes. By default Java "
+                                      + "routes are read by the Java DSL 
parser, without compiling them")
+    boolean compile;
+
     @CommandLine.Option(names = { "--errors" },
                         description = "Draw the error handling of each route 
in a frame below its steps: its onException clauses,"
                                       + " where its failures go and where it 
is reached on error from. Without it only the steps"
@@ -396,13 +401,12 @@ public class CamelRouteDiagramAction extends 
ActionWatchCommand {
     }
 
     /**
-     * Loads the given source files via a transient Camel boot (see
-     * {@link #dumpRoutesFromSource(List, String, boolean)}) and returns the 
merged route structure across all of them,
-     * or {@code null} if a file does not exist or loading failed (an error is 
already printed to the user in that
-     * case). Package visible for testing.
+     * Loads the given source files (see {@link #dumpRoutesFromSource(List, 
String, boolean, boolean)}) and returns the
+     * merged route structure across all of them, or {@code null} if a file 
does not exist or loading failed (an error
+     * is already printed to the user in that case). Package visible for 
testing.
      */
     List<RouteInfo> doCallSource(List<String> sourceFiles) throws Exception {
-        SourceDump dump = dumpRoutesFromSource(sourceFiles, 
"route-diagram-source-", ignoreLoadingError);
+        SourceDump dump = dumpRoutesFromSource(sourceFiles, 
"route-diagram-source-", ignoreLoadingError, compile);
         if (dump == null) {
             return null;
         }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java
index fc8fcc793a05..08a5ef1cfad9 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteStructureAction.java
@@ -26,6 +26,7 @@ import java.util.Objects;
 
 import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
 import org.apache.camel.dsl.jbang.core.commands.Run;
+import org.apache.camel.dsl.jbang.core.commands.TransformJavaRoutes;
 import org.apache.camel.dsl.jbang.core.common.CamelJBangConstants;
 import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
 import org.apache.camel.dsl.jbang.core.common.PathUtils;
@@ -88,6 +89,11 @@ public class CamelRouteStructureAction extends 
ActionBaseCommand {
                         description = "Whether to ignore route loading and 
compilation errors (use this with care!)")
     boolean ignoreLoadingError;
 
+    @CommandLine.Option(names = { "--compile" }, defaultValue = "false",
+                        description = "Whether to compile Java source files 
and load their routes. By default Java "
+                                      + "routes are read by the Java DSL 
parser, without compiling them")
+    boolean compile;
+
     @CommandLine.Option(names = { "--sort" }, completionCandidates = 
NameIdCompletionCandidates.class,
                         description = "Sort route by name or id", defaultValue 
= "name")
     String sort;
@@ -128,6 +134,7 @@ public class CamelRouteStructureAction extends 
ActionBaseCommand {
         JsonObject jo = waitForOutputFile(outputFile);
         if (jo != null) {
             if (jsonOutput) {
+                addUnreadSourceParts(jo);
                 String dump = Jsoner.prettyPrint(jo.toJson(), 2);
                 printer().println(dump);
             } else {
@@ -224,6 +231,9 @@ public class CamelRouteStructureAction extends 
ActionBaseCommand {
         }
 
         final String target = CommandLineHelper.CAMEL_JBANG_WORK_DIR + 
"/structure-output.json";
+        if (!compile && TransformJavaRoutes.applies(List.of(name))) {
+            return dumpJavaRoutes(List.of(name), target) ? 0 : 1;
+        }
 
         Run run = new Run(getMain()) {
             @Override
@@ -339,4 +349,8 @@ public class CamelRouteStructureAction extends 
ActionBaseCommand {
         String description;
     }
 
+    @Override
+    protected boolean printSourceNotes() {
+        return !jsonOutput;
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteTopologyAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteTopologyAction.java
index e4af2f17c6be..281e23e578f0 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteTopologyAction.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelRouteTopologyAction.java
@@ -115,6 +115,11 @@ public class CamelRouteTopologyAction extends 
ActionBaseCommand {
                         description = "Whether to ignore route loading and 
compilation errors (use this with care!)")
     boolean ignoreLoadingError;
 
+    @CommandLine.Option(names = { "--compile" }, defaultValue = "false",
+                        description = "Whether to compile Java source files 
and load their routes. By default Java "
+                                      + "routes are read by the Java DSL 
parser, without compiling them")
+    boolean compile;
+
     private long pid;
 
     public CamelRouteTopologyAction(CamelJBangMain main) {
@@ -160,7 +165,7 @@ public class CamelRouteTopologyAction extends 
ActionBaseCommand {
     }
 
     private int doCallSource(List<String> sourceFiles) throws Exception {
-        SourceDump dump = dumpRoutesFromSource(sourceFiles, 
"route-topology-source-", ignoreLoadingError);
+        SourceDump dump = dumpRoutesFromSource(sourceFiles, 
"route-topology-source-", ignoreLoadingError, compile);
         if (dump == null) {
             return 1;
         }
@@ -193,6 +198,7 @@ public class CamelRouteTopologyAction extends 
ActionBaseCommand {
      */
     int renderTopology(JsonObject jo) throws Exception {
         if (jsonOutput) {
+            addUnreadSourceParts(jo);
             String dump = Jsoner.prettyPrint(jo.toJson(), 2);
             printer().println(dump);
         } else if (theme != null) {
@@ -369,4 +375,8 @@ public class CamelRouteTopologyAction extends 
ActionBaseCommand {
         }
     }
 
+    @Override
+    protected boolean printSourceNotes() {
+        return !jsonOutput;
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java
new file mode 100644
index 000000000000..40969d254666
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/RouteActionJavaSourceTest.java
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.action;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.apache.camel.diagram.RouteDiagramLayoutEngine.RouteInfo;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.StringPrinter;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.apache.camel.util.json.Jsoner;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * route-diagram, route-topology and route-structure read Java source files 
with the Java DSL parser (CAMEL-25565): the
+ * routes are dumped from a CamelContext that is never started, so nothing is 
compiled and a source that does not
+ * compile (here: a processor class that does not exist) still gets its 
diagram.
+ */
+class RouteActionJavaSourceTest {
+
+    private static final String ORDERS = """
+            import org.apache.camel.builder.RouteBuilder;
+
+            public class Orders extends RouteBuilder {
+                @Override
+                public void configure() {
+                    from("timer:orders").routeId("intake")
+                        .setBody(constant("order"))
+                        .process(exchange -> 
exchange.getMessage().setHeader("checked", true))
+                        .to("direct:billing");
+                }
+            }
+            """;
+
+    private static final String BILLING = """
+            import org.apache.camel.builder.RouteBuilder;
+
+            public class Billing extends RouteBuilder {
+                @Override
+                public void configure() {
+                    from("direct:billing").routeId("billing")
+                        .process(new NoSuchProcessor())
+                        .log("${body}");
+                }
+            }
+            """;
+
+    private static final String MIXED = """
+            import org.apache.camel.builder.RouteBuilder;
+
+            public class Mixed extends RouteBuilder {
+                @Override
+                public void configure() {
+                    from("timer:tick").routeId("named")
+                        .to("direct:next");
+
+                    from("direct:next")
+                        .log("${body}");
+                }
+            }
+            """;
+
+    @Test
+    void diagramReadsJavaRoutesWithoutCompilingThem(@TempDir Path dir) throws 
Exception {
+        Path orders = write(dir, "Orders.java", ORDERS);
+        Path billing = write(dir, "Billing.java", BILLING);
+        StringPrinter printer = new StringPrinter();
+        CamelRouteDiagramAction command = new CamelRouteDiagramAction(new 
CamelJBangMain().withPrinter(printer));
+
+        List<RouteInfo> routes = 
command.doCallSource(List.of(orders.toString(), billing.toString()));
+
+        assertThat(routes).extracting(r -> 
r.routeId).containsExactlyInAnyOrder("intake", "billing");
+        RouteInfo intake = routes.stream().filter(r -> 
"intake".equals(r.routeId)).findFirst().orElseThrow();
+        // the lambda is a placeholder step, so the route keeps all its steps
+        assertThat(intake.nodes).extracting(n -> n.type).contains("from", 
"setBody", "process", "to");
+        assertThat(printer.getOutput())
+                .contains("Routes read without compiling them")
+                .contains("Orders.java:")
+                .contains("a lambda or method reference");
+    }
+
+    @Test
+    void diagramCompilesWithCompileOption(@TempDir Path dir) throws Exception {
+        Path missing = dir.resolve("Missing.java");
+        StringPrinter printer = new StringPrinter();
+        CamelRouteDiagramAction command = new CamelRouteDiagramAction(new 
CamelJBangMain().withPrinter(printer));
+        command.compile = true;
+
+        // the existence check comes first, also when compiling
+        assertThat(command.doCallSource(List.of(missing.toString()))).isNull();
+        assertThat(printer.getOutput()).contains("File does not exist: " + 
missing);
+    }
+
+    @Test
+    void diagramReportsJavaSourceWithoutRoutes(@TempDir Path dir) throws 
Exception {
+        Path helper = write(dir, "Helper.java", """
+                public class Helper {
+                    public static String name() {
+                        return "helper";
+                    }
+                }
+                """);
+        StringPrinter printer = new StringPrinter();
+        CamelRouteDiagramAction command = new CamelRouteDiagramAction(new 
CamelJBangMain().withPrinter(printer));
+
+        assertThat(command.doCallSource(List.of(helper.toString()))).isNull();
+        assertThat(printer.getOutput())
+                .contains("No routes found")
+                .contains("Helper.java has no routes the parser can read")
+                .contains("--compile");
+    }
+
+    @Test
+    void topologyLinksJavaRoutesOfSeveralFiles(@TempDir Path dir) throws 
Exception {
+        Path orders = write(dir, "Orders.java", ORDERS);
+        Path billing = write(dir, "Billing.java", BILLING);
+        StringPrinter printer = new StringPrinter();
+        CamelRouteTopologyAction command = new CamelRouteTopologyAction(new 
CamelJBangMain().withPrinter(printer));
+        command.files = List.of(orders.toString(), billing.toString());
+        command.jsonOutput = true;
+
+        int exit = command.doCall();
+
+        assertThat(exit).isZero();
+        // with --json only the JSON is printed, and the parts the parser 
could not read are listed in it
+        JsonObject topology = (JsonObject) 
Jsoner.deserialize(printer.getOutput());
+        JsonArray edges = (JsonArray) topology.get("edges");
+        assertThat(edges).hasSize(1);
+        JsonObject edge = (JsonObject) edges.get(0);
+        assertThat(edge.getString("fromRouteId")).isEqualTo("intake");
+        assertThat(edge.getString("toRouteId")).isEqualTo("billing");
+        JsonArray unread = (JsonArray) topology.get("unread");
+        assertThat(unread).isNotEmpty();
+        assertThat(unread.get(0).toString()).contains("Orders.java:");
+    }
+
+    @Test
+    void structureReadsJavaRoutesWithoutCompilingThem(@TempDir Path dir) 
throws Exception {
+        Path billing = write(dir, "Billing.java", BILLING);
+        StringPrinter printer = new StringPrinter();
+        CamelRouteStructureAction command = new CamelRouteStructureAction(new 
CamelJBangMain().withPrinter(printer));
+        command.name = billing.toString();
+
+        int exit = command.doCall();
+
+        assertThat(exit).isZero();
+        assertThat(printer.getOutput())
+                .contains("Source: Billing.java")
+                .contains("billing")
+                .contains("direct:billing")
+                .contains("an object created in the route");
+    }
+
+    @Test
+    void diagramReadsJavaRoutesWithoutRouteId(@TempDir Path dir) throws 
Exception {
+        Path mixed = write(dir, "Mixed.java", MIXED);
+        StringPrinter printer = new StringPrinter();
+        CamelRouteDiagramAction command = new CamelRouteDiagramAction(new 
CamelJBangMain().withPrinter(printer));
+
+        List<RouteInfo> routes = 
command.doCallSource(List.of(mixed.toString()));
+
+        // the route without an id gets one, as when Camel starts, and the 
named route is kept
+        assertThat(routes).extracting(r -> 
r.routeId).hasSize(2).contains("named").doesNotContainNull();
+    }
+
+    @Test
+    void structureReadsJavaRouteWithoutRouteId(@TempDir Path dir) throws 
Exception {
+        Path mixed = write(dir, "Mixed.java", MIXED);
+        StringPrinter printer = new StringPrinter();
+        CamelRouteStructureAction command = new CamelRouteStructureAction(new 
CamelJBangMain().withPrinter(printer));
+        command.name = mixed.toString();
+        // the default of --sort (two routes are sorted)
+        command.sort = "name";
+
+        int exit = command.doCall();
+
+        assertThat(exit).isZero();
+        assertThat(printer.getOutput())
+                .contains("Source: Mixed.java")
+                .contains("named")
+                .contains("timer:tick")
+                .contains("direct:next");
+    }
+
+    private static Path write(Path dir, String name, String content) throws 
Exception {
+        Path file = dir.resolve(name);
+        Files.writeString(file, content);
+        return file;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramTools.java
 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramTools.java
index 9d4e5a56491d..302f287edbfb 100644
--- 
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramTools.java
+++ 
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/RouteDiagramTools.java
@@ -42,7 +42,9 @@ public class RouteDiagramTools {
 
     @Tool(annotations = @Tool.Annotations(readOnlyHint = false, 
destructiveHint = false, openWorldHint = false),
           description = "Generate a diagram of Camel routes from a route 
source file (YAML, XML, Java, ...). "
-                        + "The source file is parsed without running it. "
+                        + "Java routes are read by the Java DSL parser, 
without compiling them; a step it cannot "
+                        + "work out (a lambda, a processor) is shown as a 
placeholder, and the parts it could not read "
+                        + "are listed in the message. "
                         + "For image themes (dark, light, transparent) the PNG 
is written to disk and the absolute path "
                         + "is returned. For text themes (ascii, unicode) the 
diagram is returned directly as text so "
                         + "it can be read and understood by the caller. "
@@ -120,13 +122,13 @@ public class RouteDiagramTools {
             if (textMode) {
                 String asciiContent = success ? Files.readString(out.toPath()) 
: null;
                 String message = success
-                        ? "ASCII diagram generated (" + size + " bytes)"
+                        ? withNotes("ASCII diagram generated (" + size + " 
bytes)", capturingPrinter)
                         : "Failed to render diagram (exit code " + exit + "): 
" + capturingPrinter.getOutput();
                 return new RouteDiagramResult(success, resolvedOutput, size, 
message, asciiContent);
             }
 
             String message = success
-                    ? "Diagram saved to: " + resolvedOutput
+                    ? withNotes("Diagram saved to: " + resolvedOutput, 
capturingPrinter)
                     : "Failed to render diagram (exit code " + exit + "): " + 
capturingPrinter.getOutput();
             return new RouteDiagramResult(success, resolvedOutput, size, 
message, null);
         } catch (Throwable e) {
@@ -135,6 +137,14 @@ public class RouteDiagramTools {
         }
     }
 
+    /**
+     * The message, followed by what the command printed, such as the parts of 
a Java route the parser could not read.
+     */
+    private static String withNotes(String message, CapturingPrinter printer) {
+        String notes = printer.getOutput();
+        return notes.isEmpty() ? message : message + "\n" + notes;
+    }
+
     /**
      * @param asciiDiagram plain-text diagram content for ascii/unicode 
themes; null for image themes
      */

Reply via email to