You have an existing Graphitron subgraph and you want a tight edit-save-see-it loop. The short version: run two long-lived terminals, one for mvn graphitron:dev (regenerates Java from your .graphqls, serves an LSP to your editor, and serves an MCP server to your agent), one for mvn quarkus:dev (hot-reloads the running app). Edit the schema, save, and the change flows through both without restarting anything.
|
On macOS the filesystem-watch half of this loop is slow on its own. Attaching your editor to the LSP is what makes saves feel instant. See Why macOS needs the LSP. |
TLDR
| Step | What to run |
|---|---|
1. Regenerate + LSP |
|
2. Run the app |
|
3. Connect your editor |
Point your LSP client at |
4. Connect your agent (optional) |
Point an MCP-aware agent (Claude Code, Cursor) at |
5. Edit and save |
Save a |
Both Maven goals are long-lived. Start them once at the beginning of a session and leave them running; stop with Ctrl+C.
The loop in detail
mvn graphitron:dev is one JVM, one terminal, one socket. It binds an LSP server on localhost:8487, watches your schema sources, and on every save re-runs the generator. Writes are idempotent: only files whose rendered content actually changed touch disk (SHA-256 comparison), and types you delete from the schema have their generated files removed. So git diff after a session shows only what the edit predicts, and downstream tools recompile only what changed.
That idempotence is exactly what lets quarkus:dev sit on top. Quarkus detects source-root changes by mtime; because unchanged generated files keep their mtimes, a one-type schema edit triggers a reload of just that type’s classes, not the whole schema. The same is true of IntelliJ’s incremental compiler and Spring Boot DevTools, so no Graphitron-specific build plugin is needed for the reload half.
If 8487 is already in use, pass -Dgraphitron.dev.port=N to mvn graphitron:dev and use the matching port in your editor config. The goal fails fast on a bind conflict with a message naming the property; it never silently rebinds to a port the editor would not know about.
For the full picture of how the schema watcher, classpath watcher, and generator dispatch cooperate inside the dev goal, see the dev loop reference in the architecture docs.
Compiled generated classes
graphitron:dev compiles the generated code, so the dev tools can run it. In a dev session the goal writes the generated Java and compiles it into its own output directory, target/graphitron-classes/<outputPackage>/, keeping a runnable image of your whole schema as you edit. Only the classes an edit touches are recompiled, so a one-field change is fast even on a large schema. The immediate payoff: the dev loop’s MCP tools can execute a query against your resolvers in-process, with no app server and no quarkus:dev needed to see what a query returns.
Do you need to configure anything? Usually not. Graphitron writes to its own directory and never touches a file your build owns, so there is nothing to guard against and no required setup. Two situations where a one-liner helps:
-
Spring Boot DevTools, plain
java, IDE runs. These load.classoff the classpath, so puttingtarget/graphitron-classesahead oftarget/classeson the run classpath lets your running app use Graphitron’s fresh classes directly. Ordering is what matters; Graphitron’s copy wins over any copy your own build produced. -
Quarkus dev (
quarkus:dev). Do not add Graphitron’s directory to the Quarkus classpath. Quarkus recompiles the generated sources itself and will not pick up an external.class, so front-loading our directory buys nothing and only risks shadowing Quarkus’s own reload. Runquarkus:devexactly as today; Graphitron’s compile still powers the in-process MCP query tools running alongside it.
Optional: skip the redundant compile. Independently of the above, you can stop your own build from re-compiling the generated sources (they stay a source root for IDE indexing and go-to-definition either way). This only saves double-work; it is never required for correctness, because Graphitron writes to a separate directory:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/<outputPackage-as-path>/**</exclude>
</excludes>
</configuration>
</plugin>
If you skip the classpath step, nothing breaks. Your app runs exactly as today: your build compiles the generated sources into target/classes and runs from there, while Graphitron’s separate output still powers the in-process MCP query tools; it just is not fed to your running app. There is no race and no corruption to guard against, because the two never write the same file.
To turn Graphitron’s compile off entirely (giving up the MCP query tools too), run with -Dgraphitron.dev.compile=false. Because the output directory is Graphitron’s alone, there is no cross-writer race and no fail-fast check: every misconfiguration degrades to today’s generate-only behaviour rather than corrupting bytecode.
Why macOS needs the LSP
Java’s WatchService has no native file-event backend on macOS; it falls back to a polling implementation that only notices a change on its next poll. In practice a save can take several seconds to register, which makes the filesystem-watch path of the dev loop feel laggy on a Mac in a way it does not on Linux.
The LSP path sidesteps this entirely. When your editor is attached to the LSP on localhost:8487, the editor’s save event drives the regeneration directly, instead of waiting for the polling watcher to notice the file changed on disk. So on macOS, attaching your editor to the LSP is not just for diagnostics and completion: it is what restores an instant save-to-regen loop. The schema watcher still runs as a headless fallback for saves made outside the attached editor; it is just slower on this platform.
Linux and Windows have native watch backends and do not suffer this latency, but connecting the editor is still worth it for the diagnostics, hover, completion, and go-to-definition the LSP provides.
Connect your editor to the LSP
The LSP speaks over TCP on localhost:8487. Any LSP client that can connect to a TCP socket works: point it at that address and register it for .graphql and .graphqls files. Neovim, Helix, and VS Code can target a TCP socket directly. IntelliJ needs one extra hop, covered next.
IntelliJ users
IntelliJ’s LSP support (both the Ultimate built-in API and the LSP4IJ plugin) launches a language server as a subprocess and talks to it over stdin/stdout; neither connects to a TCP socket out of the box. The simplest bridge is netcat: a nc 127.0.0.1 8487 subprocess copies bytes between IntelliJ’s stdio and the dev server’s socket.
Set it up with LSP4IJ (Red Hat), which works in both IntelliJ Community and Ultimate:
-
Install LSP4IJ from Settings → Plugins → Marketplace.
-
Open Settings → Languages & Frameworks → Language Servers, add a new server, and import (or fill in) this configuration:
{ "name": "Graphitron", "programArgs": { "default": "sh -c \"nc 127.0.0.1 8487\"" }, "languageMappings": [ { "language": "GraphQL", "languageId": "graphql" } ], "fileTypeMappings": [ { "fileType": { "patterns": [ "*.graphql" ] }, "languageId": "graphql" }, { "fileType": { "patterns": [ "*.graphqls" ] }, "languageId": "graphql" } ] }The
programArgscommand is what bridges stdio to the dev server’s TCP socket; the mappings register the server for.graphqland.graphqlsfiles. -
Start
mvn graphitron:devin a terminal first (the bridge connects to it; if the dev goal is not running,ncexits immediately and IntelliJ reports the server as crashed). -
Open a
.graphqlsfile. LSP4IJ spawns the bridge, attaches, and you get diagnostics, hover, completion, and go-to-definition.
Notes:
-
ncships with macOS and most Linux distributions. On Windows, install a netcat (for examplencatfrom Nmap) and adjust the command, or use an editor that connects to TCP directly. -
If you overrode the port with
-Dgraphitron.dev.port=N, change8487in the command to match. -
The dev goal serves all connected clients off the same warm catalog, so you can attach IntelliJ and another editor at once; a save in either is seen by both.
-
A native IntelliJ plugin that removes the manual LSP4IJ + netcat setup is on the roadmap; until it ships, this is the supported IntelliJ path.
Connect your agent to the MCP server
The same mvn graphitron:dev process that serves the LSP also runs an MCP server on http://127.0.0.1:8488/mcp (Streamable HTTP, loopback only). An MCP-aware agent (Claude Code, Cursor) connected to it gets read-only context about your project: it can search the bundled manual (docs.search), discover tables in your jOOQ catalog (catalog.search, catalog.describe), inspect the classified schema, @service / @condition / @record bindings, and the current validation diagnostics, plus a /mcp__graphitron__about slash command that orients it on the loop. Point the dev loop at a database and the agent can also run queries against your generated resolvers in-process (the execute tool; mutations always roll back), closing the loop from schema edit to real query result without an app server. See Run a query without an app server.
For Claude Code, run the line the dev server prints on startup:
claude mcp add --transport http graphitron http://127.0.0.1:8488/mcp
Unlike the LSP port, the MCP port is fixed at 8488 and not overridable, so a committed .mcp.json stays valid across machines. If 8488 is already taken, the dev goal fails fast naming the conflict rather than rebinding.
The full setup, the complete tool list, and the .mcp.json for zero-config sharing are in Agent context over MCP.
See also
-
Agent context over MCP: connect an MCP-aware agent to the same dev server.
-
Dev loop reference (architecture docs): how the
devgoal is wired internally. -
LSP inlay hints: the editor hints the LSP surfaces.
-
Test your schema: the consumer-side execution test that complements the inner loop.
← How-to index </content> </invoke>