Activates in devSpring: dev / local or DevTools. Quarkus: dev / test modes.
Serves two thingsThe console at /bootui, the JSON at /bootui/api/**
Dark in productionAmbiguous activation leaves it disabled
🧱 How one codebase serves three stacks
Vue 3 single-page console — /bootui
One UI. It does not know which framework is underneath it.
▲ one JSON contract · /bootui/api/** ▲
Spring MVC
Reference stack
Spring WebFlux
Reactive adapter
Quarkus
Runtime + deployment
▲ thin, and native to their framework ▲
bootui-engine
Framework-neutral DTOs, capture, runtime journal, masking, advisors, scoring and MCP
Optional: bootui-agent + bootui-agent-bridge
Explicit -javaagent attachment. Dormant until claimed; adds execution and method evidence.
The same records serialize byte-identically under Jackson 3 (Spring Boot 4) and
Jackson 2 (Quarkus) — which is why the UI never forks.
Where a stack genuinely cannot answer, the panel stays visible and says why —
never an empty table that looks like "no traffic yet".
03
Capture honestly.
Three engineering signatures run through every panel that follows.
1 · Own the capture
No p6spy, no datasource-proxy, no HTTP proxy library. Framework SPIs and hand-written JDK proxies.
2 · Open on capture, closed on data
Can't instrument? Leave the app untouched. Can't answer? Report unavailable — never an empty table.
3 · Label your confidence
Tiered correlation everywhere, with explicit Unattributed and Ambiguous buckets.
We start with the Scorecard and Advisors, then Live Activity and Runtime Insights, Runtime,
Database, Security, Services, Diagnostics and Instrumentation. Configuration shows up in the demo, and
Developer tools gets its own part.
Part 03 · Inside
🩺 Advisors — findings with fixes, on demand
Hundreds of rules, written with AI
Every rule is its own class with a stable id, a severity and a
concrete recommendation — including 48 Database checks in 2.2
Written with AI, then reviewed one rule at a time: the strongest
models applied to framework expertise, not a generic linter
Hibernate and Database show new, resolved and unchanged findings
since the previous scan; individual findings can be dismissed and restored
Findings tied to
your beans, queries and configuration, so you can actually
improve the application
One score out of 100
9 severity-scored scanners, each starting at 100
Fixed penalties: critical −25 · high −10 · medium −3 · low −1
Overall = the mean of eligible known-findings scores — plus GitHub's alert score when eligible
🛑 Nothing is scanned on page load.
Before any run, the Scorecard reads Not scored — never a fake zero. A score summarizes known findings, not application safety or complete coverage.
Slow rows tinted on a heat scale at 100 / 200 / 500 / 1000 ms
An N+1 badge on the row itself — same rule the profiler uses, so the two never disagree
Refreshes over Server-Sent Events, not a polling timer
Click a request → the drawer
Every correlation is labelled with how it was established:
Request idexactStamped when the signal was recorded
PropagatedexactExecutor work followed by the optional Java agent
Trace idexactA trace id identifying one captured request
Serving threadexactThe one worker that served it
Time windowapproximateMethod, path and window
It degrades gracefully and never fabricates a correlation
⚡ The runtime journal is on by default, bounded to the smaller of 32 MB and
5% of heap. Signals stay masked and bounded; missing correlation stays explicit.
🔎 Runtime Insights — what this run actually did
28 evidence-backed observations
Repeated SELECTs, errors behind a 2xx, transactions across remote calls, blocking on event loops
2.2 adds slow flushes, connection reacquisition, unbatched writes, read-only candidates and paging without order
Each observation names its evidence, exemplar requests, checks to perform and coverage limits
Did the change make a difference?
Changes compares retained runs; Change impact lists the routes that exercised a resource
With the Java agent: changed methods, whether they ran, and newly observed side effects
Copy for AI, MCP and CLI expose the same facts — not an invented root cause
Opening the panel starts no scan, probe or network call.
It reads retained evidence. Missing evidence is not a clean bill of health.
🔬 Runtime — ten panels on the running JVM
Memory, threads, startup, metrics and native-image readiness — read in-process, without attaching a profiler.
What the group covers
Live Memory and Threads — heap, pools and deadlocks; low-footprint mode pauses recording and frees its memory
Heap Dump — capture and analyse locally, class histogram only, never object values
Startup Timeline and Metrics — where the seconds went, and every Micrometer meter grouped by who registered it
Health and HTTP Sessions — the full contributor tree; sessions masked by default
GraalVM and CRaC — 30 and 19 curated readiness checks
🎚️ And one that prescribes: JVM Tuning
Partitions a target process budget into heap, metaspace, code cache, direct memory, thread stacks and headroom — then hands you copyable flags
Bare metal: fixed -Xms / -Xmx, keeping your collector
Kubernetes: equal request and limit, with MaxRAMPercentage in JAVA_TOOL_OPTIONS
Labels Pod QoS Depends on CPU instead of claiming Guaranteed it can't verify
🐘 PostgreSQL & MySQL — what the server itself says
The server's own numbers
One card per datasource: vital signs always in view, everything
else in tabs — sessions and blocking, statement ranking, index usage, table sizes, replication,
settings
PostgreSQL reads pg_stat_* and
pg_catalog, including autovacuum health, checkpoints and WAL
MySQL reads Performance Schema and Information Schema, plus
InnoDB evidence — tested on 8.4 LTS
No rules, no severity, no score: the server's numbers, your
judgement
Explicit, bounded, read-only
Opening the panel shows the last report — the database is queried only when you press
Run read
PostgreSQL runs one read-only transaction: statement_timeout 5s,
lock_timeout 2s, a 15 s budget, each query in its own
savepoint
Missing pg_monitor rights or a capped section is
partial, and an unknown value stays unknown — never a silent
zero
🧭 Three angles on one database: the Database advisor checks the schema ·
SQL Trace shows what this JVM sent ·
PostgreSQL and MySQL show what the server saw from every client.
All three stacks, through your existing JDBC datasources. The panel only appears when that vendor's driver is on
the classpath. MariaDB, R2DBC-only and reactive-client-only apps are not supported.
🔄 Transactions — the boundaries nobody shows you
Captured by Spring's own SPI
A TransactionExecutionListener (Spring Framework 6.1+) — BootUI's own wiring, not a third-party observability library
It composes with, never replaces, your transaction management and your own listeners
Managers that don't implement the SPI simply stay unobserved — nothing is monkey-patched to force it
What a boundary records
A parent/child tree — nested calls sit under their root
Propagation as NEW or PARTICIPATING — and it says plainly that Spring's SPI does not expose the declared @Transactional enum
Isolation at begin, outcome (COMMITTED / ROLLED_BACK / UNKNOWN), thread, trace id
🔗 Each transaction is correlated to SQL Trace by thread and time window — so you see how many
statements and how many JDBC connections a boundary touched,
without instrumenting anything twice.
Fails closed with a clear reason when there's no PlatformTransactionManager, or on a WebFlux app using only R2DBC.
Honestly not applicable on Quarkus: Narayana and the CDI interceptor expose no comparable per-boundary hook.
🗄️ SQL Trace — no proxy library, no guessing
Capture
A hand-written JDBC tracer on the JDK's own dynamic proxies — BootUI bundles no database-proxy library
Wrapping fails open: a DataSource that can't be proxied is left untouched rather than breaking startup
Works in a GraalVM native image — the proxies are registered as native metadata
Quarkus reaches parity with two feeders: an Agroal @Alternative plus a Hibernate StatementInspector, because ORM SQL bypasses the CDI DataSource
Read
Rankings normalize literals and bind markers to ? and fold IN (…) — equivalent statements group without ever exposing a bound value
Database time by request route — which endpoint is spending your database budget
N+1 groups list the distinct call sites: class, method, line
Unplaceable statements go to explicit Unattributed and Ambiguous buckets
🔐 Security — who can reach what, and who got refused
Spring Security — the chains, explained
Filter chains and per-endpoint rule explanations, without exposing credentials
Explain builds a stub request carrying only the path and method —
never your headers, cookies, principal or session
What it can't determine is marked best effort, not guessed
Quarkus has no filter chains: not applicable, and the advisor swaps
in Quarkus-native checks
Security Logs — who was refused, and when
Authentication successes and failures, authorization denials — filtered by principal, type and
time window
Live over SSE: the browser re-fetches when the server signals a new
audit event, instead of polling on a timer
Bounded to 500 events, masked before rendering
Quarkus is honestly partial: CDI security events carry no
logout or session equivalent, and the panel says so
🔌 Support for all the main services your app uses
Scheduled TasksExecutions, not just declarations
REST ClientOutbound HTTP calls
Fault ToleranceBreakers, retries, bulkheads
WebSocketsSessions and frames
AI FrameworkSpring AI · LangChain4j
CacheHit ratios and tiering
EmailCaptured outgoing mail
KafkaProduced and consumed
RabbitMQPublishes and deliveries
JMSSpring messaging
Still no proxy libraries
Spring's own RestClientCustomizer / RestTemplateCustomizer; on Quarkus, the MicroProfile RestClientListener SPI
Email decorates JavaMailSender and records before delegating — pass-through by default
A capture failure never disrupts the call itself
Distinctions that matter
Failed (connection refused, timeout, DNS) is counted separately from Error responses (4xx/5xx) — most tools blur these
chatty is flagged for any method: a looped POST costs as much as a looped GET
Email's opt-in dev-trap is off by default, so BootUI never silently swallows your mail
💥 Exceptions — triage, with regression detection
Grouped, not streamed
A stable fingerprint from exception type + top frames collapses a recurring error into one row
Two sources on Spring MVC: a non-intrusive HandlerExceptionResolverand a logback appender for scheduled, async and log.error(…, ex)
A failure that is both handled and logged is de-duplicated by throwable identity — counted once
Application frames highlighted; full cause chain with … N more folding
Sentry-style status, one click
Open
Default for every new group
Acknowledged
Seen — keeps accumulating, never auto-transitions
Resolved
Believed fixed
If a Resolved group throws again, it regresses: back to Open, and a lifetime
Reopened ×N counter appears on the badge.
Bounded on purpose: 100 groups, 25 occurrences each, 50 frames — the
least-recently-seen group is evicted
Clear is an explicit action, and it honours the panel's read-only setting
🔁 A failure you thought you had fixed announces itself.
That is the whole feature.
Triage and regression live in the engine, so they behave identically on all three stacks — only capture sources differ.
WebFlux carries one documented gap: a controller's own @ExceptionHandler consumes the exception before any WebExceptionHandler sees it.
🔬 Optional Java agent — did my change actually run?
Opt in at JVM startup
mvn dependency:get \
-Dartifact=com.julien-dubois.bootui:bootui-agent:2.2.0
# VM option — use the jar matching BootUI's version
-javaagent:/path/to/bootui-agent-2.2.0.jar
Development JVMs only — not production or AOT/native-image runs
Dormant until BootUI claims it; status and setup in the Java Agent panel
No agent attached? The embedded console still works.
What the extra evidence buys you
Code Paths: method timings and call trees per request
Code Inventory: changed methods, whether they ran, and loaded dependency classes
Side Effects: processes, network, file patterns, blocking calls and resource leaks
Executor propagation follows work after the response; bounded method probes need separate approval
🎬 Demo — five minutes, four beats
The Spring sample app on 2.2.0, running locally with its matching Java agent. Nothing pre-scanned.
1Understand an unfamiliar app. Check the Java Agent status, then Beans and Conditions: what is wired, and why that auto-configuration was skipped.
2Run all scanners. Watch the score appear from nothing, then open one real finding and read its recommendation.
3Trigger a slow request with an N+1. Live Activity shows the row tinted and badged; click it to open the profiler drawer, and follow it into SQL Trace — down to the call site.
4Exercise a change and verify it. Runtime Insights → Changes and Code Inventory: which edited methods ran, on which route? If none ran, don't claim the fix worked.
🛟 The next slides show the console and Runtime Insights — if the demo gods are unkind,
keep talking and keep moving.
Focus a bean and see only its neighbourhood — dependencies, dependents, scope, and
why this bean exists. Not a 400-node hairball.
Fallback · Beat 2Run all scanners — the score appears from nothing
A known-findings score, with coverage stated separately.
Before the run it reads Not scored, never a fake zero. A high score is not a safety guarantee.
Fallback · Beat 3aLive Activity — vital signs, and a live service map
The map is assembled from evidence BootUI already retained — opening it
contacts nothing and probes nothing.
Fallback · Beat 3bSQL Trace — the N+1, down to the call site
possible N+1 on a normalized statement — with
OrderService named as the call site, and not one bound parameter on screen.
Fallback · ExceptionsExceptions — resolved, then thrown again
Resolve a group, then throw it again: the regression reopens it.
With the opt-in caught-exceptions sensor, application catch handlers add another source of evidence.
Fallback · Beat 4Runtime Insights — evidence, changes and coverage
Read the observation and its evidence, then use Changes and
Change impact to investigate the next run. Not exercised is not verified.
04
Your agent reads the same panels.
An AI agent reading only your source code is guessing about the runtime.
Part 04 · The second user
🤖 The MCP server — opt-in, loopback, same evidence
Disabled by default. Flip it on in the panel for the life of the process
No credentials needed on loopback — the panel shows the config, ready to copy
What it exposes
Scan
advisors, with compact summaries and cached report details
Diagnose
activity, request profiles, exceptions, SQL and traces
Compare
Runtime Insights, change impact and retained runs
Verify
Java-agent status, Code Inventory, Code Paths and side effects
Control
bounded actions, sensor switches and method probes
Tools whose panel is absent are simply not advertised.
1shared registry · loopback endpoint
0screenshots to interpret
🛡️ Same controllers. Same masking. Same rules — action tools are refused when a panel is read-only,
and sensitive actions need separate approval. No heap-dump capture, HTTP probes, database mutation or cache clearing.
⌨️ The bootui CLI — one question, one answer
Ask the running app from a terminal
# Linux and macOS — or JBang, or the jar
curl -fsSL https://www.julien-dubois.com/boot-ui/install.sh | sh
bootui beans --query dataSource
bootui http exchanges --limit 20
bootui hibernate scan --fail-on high --json
bootui insights compare --json
bootui tools # what this app answers
One runnable jar, JDK 17+. No MCP client needed; Java-agent-backed reads need its instrumentation.
Built for scripts and CI
One diagnostic command per MCP tool, generated from the same registry —
a test fails if the two ever diverge
Tables on a terminal; the application's exact JSON bytes when piped
or with --json
Exit codes a pipeline can branch on: 0 answered ·
1 error · 2
BootUI declined — panel off or read-only, so CI treats it as a
skip · 3 severity threshold reached
--fail-on high gates scans and reports — an unevaluable gate exits 1, not a silent pass
🛡️ Not a new exposure: the same loopback, Host and token guards, the same per-panel
policy, the same masking — a second spelling of the same panel data.
🔁 The loop that closes
1Consult. Read get_runtime_insights and the relevant advisor. Check get_agent_status before asking for Java-agent evidence.
2Correlate. Open an exemplar with get_request_profile. Read its SQL, exceptions and coverage limits instead of inferring the cause from a symptom.
3Fix. It edits your source with runtime evidence instead of a guess about what the code probably does.
4Exercise, then verify. Rerun the request or test. With the Java agent, read get_code_inventory for changed methods; compare runs and rescan. Not executed is not fixed.
Teach the agent first
# inspect it before you trust it
gh skill preview jdubois/boot-ui skills/bootui
# then install for this project
gh skill install jdubois/boot-ui skills/bootui
Why it works
Source code says what should happen
BootUI says what is happening
The gap between those two is where your bug lives
📚 Learn more
Read
julien-dubois.com/boot-ui — the documentation site
/features — every panel, group by group
/properties — the full configuration reference
/cli — every bootui command
/features/runtime-insights — observations and run comparison
Try
/setup — Spring Boot and Quarkus in a few lines
/try-sample-app — a running app, already wired up
Or just add the dependency to a project you already have
/setup/java-agent — optional instrumentation and its limits
Wire up your agent
/ai-agents — the MCP server, end to end
Consumer skill, Claude Code and Cursor plugins, or any MCP client
Off by default — one property turns it on
⭐ If any of this was useful, star the repo — it is the single best way to help:
github.com/jdubois/boot-ui
Go turn the lights on.
Add one dependency. Open /bootui. Ask your app a question.
🟢 BootUIObserves the app from the inside — julien-dubois.com/boot-ui
🔵 CoffilotBuilds, runs and scans it from the Copilot side panel
🟤 Dr JSkillGenerates the Spring Boot app to start from