Activates in devdev / local profiles, or devtools on the classpath
Serves two thingsThe console at /bootui, the JSON at /bootui/api/**
Dark in productionAmbiguous activation leaves it disabled
🧭 58 panels, 9 groups, one question each
58
panels across 9 groups
3
stacks · one codebase
1
Vue UI · one JSON contract
0
extra processes to run
OverviewIs my app healthy — and what did it just do?
AdvisorsWhat is wrong, and how do I fix it?
RuntimeHow is the JVM behaving right now?
ConfigurationWhat config and wiring is effective?
DatabaseWhat is my app doing to the database?
SecurityHow is access actually enforced?
ServicesWhat is my app talking to?
DiagnosticsWhy did that request fail?
Developer toolsWhat is my toolchain doing locally?
Every group…answers one question, not "here is some data"
🧱 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
All reusable behaviour and policy — capture, buffers, masking, advisors, scoring, MCP
bootui-core
Immutable DTO records. No Spring. No Quarkus. No JSON library.
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".
Part 03 · Inside
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 walk the console's own menu, top to bottom — Overview, Live Activity, Runtime, Configuration, Database,
Security, Services, Diagnostics. The ninth group, Developer tools, gets its
own part.
🩺 Advisors — findings with fixes, on demand
437 rules, written with AI
Every rule is its own class with a stable id, a severity and a
concrete recommendation — Hibernate alone has 75, Security 61,
REST API 56
Written with AI, then reviewed one rule at a time: the strongest
models applied to framework expertise, not a generic linter
No other console for Spring Boot or Quarkus carries a ruleset of this depth — it is
unique to BootUI
The result is insight you cannot get elsewhere: 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 scanners that actually scored
🛑 Nothing is scanned on page load.
Before any run, the gauge shows how many scanners have scored — not a fake zero. Vulnerabilities has no fixed count: it queries OSV.dev for known CVEs and adds how likely each one is to actually be exploited.
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:
Trace idexactMicrometer traceId, threaded from the MDC
Serving threadexactThe one worker that served it
Time windowapproximateMethod, path and window
It degrades gracefully and never fabricates a correlation
⚡ Six of the nine signals reuse buffers other panels already fill — so the feed adds almost no new
instrumentation, and every value arrives already masked and bounded.
🔬 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, thread states and deadlock detection, live
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 — 27 and 17 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
⚙️ Configuration — what is actually effective
Read the truth, not the file
Every effective property with its source, metadata description and
default — and its value already masked
Profile Diff: exactly what changes between two local profiles
Conditions: positive, negative and unconditional matches — why an
auto-configuration applied, or why it was skipped
Beans and Mappings: the wiring and
the HTTP surface, read from the running route table
Then change it — with the caveat attached
Runtime overrides are persisted to
.bootui/application-bootui.properties — a real file you can read and
delete
Every mutation shows its restart and rebinding caveat, because not
every property re-binds live
Big property, bean, condition and mapping tables page server-side,
and filters run on the server — the browser never needs the full report
🫥 These panels hide BootUI's own beans, conditions, loggers and metrics
by default. The console must not show up in its own answers.
🗄️ 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
🔄 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.
🔐 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 49 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
🔭 OpenTelemetry — one pipeline, four panels
Tracing with zero configuration
The starter contributes the tracing dependencies and the sampling default — you write no management.* properties
Sampling is raised to 1.0 locally, so every request produces spans
…and it pins io.opentelemetry / io.micrometer.tracing to INFO so a DEBUG root logger doesn't flood your console
Self-traces are dropped whole: recognize one BootUI span, discard the entire trace
An OTLP receiver inside your app
POST /bootui/api/otlp/v1/traces — cooperating local services export spans into your app's console
Open a trace: a waterfall across services, with errors and parent/child spans
On Quarkus: in-process SpanProcessor with quarkus-opentelemetry — same store, same panel
🕸️ And it's the spine of the product:
tier-1 correlation for the profiler · route attribution for SQL · the only way to correlate requests on
Quarkus and WebFlux · and the source for the AI Framework panel — Spring AI and
LangChain4j GenAI spans, grouped by conversation, with token usage.
💥 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.
🎬 Demo — five minutes, four beats
The Spring sample app, running locally. Nothing pre-warmed, nothing pre-scanned.
1Understand an unfamiliar app in 90 seconds. Open /bootui — framework, version, active profiles, health. 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.
4Resolve an exception. Then throw it again. The group reopens with Reopened ×1.
🛟 The next five slides are screenshots of every beat — if the demo gods are unkind,
keep talking and keep moving.
Tools whose panel is absent are simply not advertised.
≈79tools · one loopback endpoint
0screenshots to interpret
🛡️ Same controllers. Same masking. Same rules — action tools are refused when a panel is read-only,
and the controls can never capture a heap dump, probe, mutate a database or clear a cache.
🔁 The loop that closes
1Consult. The agent calls hibernate_scan against your running app and gets severity-ranked findings with remediation hints.
2Correlate.get_live_activity and get_exception_detail(id) tie a failure to the request, the SQL and the full stack trace behind it.
3Fix. It edits your source with runtime evidence instead of a guess about what the code probably does.
4Verify. It rescans. The finding is gone, or it isn't — and it can tell the difference.
Teach the agent first
# inspect it before you trust it
gh skill preview jdubois/boot-ui bootui
# then install for this project
gh skill install jdubois/boot-ui 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
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
Wire up your agent
/ai-agents — the MCP server, end to end
Works with Copilot, Claude Code and 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