GitHub

Introduction to
BootUI

Your app is a black box. BootUI turns the lights on.

BootUI 2.2.0 — an embedded developer console for Spring Boot 4 and Quarkus, with Runtime Insights and optional Java instrumentation. 🔦

  • Developer console
  • Spring Boot 4 · Quarkus
  • Local-only · fail-closed
Julien Dubois
Principal Manager, Developer Relations at GitHub / Microsoft
github.com/jdubois/boot-ui · julien-dubois.com

Who am I?

Julien Dubois

Julien Dubois

Principal Manager, Developer Relations at GitHub / Microsoft

Java Champion GitHub / Microsoft JHipster 22k+ AI × code
  • Creator of JHipster · 22,000+ ⭐
  • 200+ international talks (Devoxx, SpringOne, Microsoft Build…)
  • Today: shipping real projects by managing AI agents
01

Why are Java applications opaque?

Spring Boot and Quarkus are missing a good developer console.

Part 01 · The problem

🕳️ Every answer lives in a different tool

The questions you actually ask

  • What configuration is actually effective right now?
  • Did that @Transactional method really commit — or quietly join someone else's transaction?
  • Is this exception new, or has it been failing all week?
  • Why was that request slow — and what SQL did it run?

What you do instead

  • curl an Actuator endpoint, read raw JSON
  • Attach jconsole / VisualVM for the JVM half
  • Tail the logs in another terminal
  • Grep the codebase to guess the wiring
Hosted dashboardsAnother service to run, for one local app
Admin serversOutside the app, shaped for production fleets
APMRight idea, wrong scale — and wrong loop
None of them live where the work happens: inside the inner loop, on localhost, for one running app.

🧰 What BootUI actually provides

One dependency. One local URL. Two ways to read your app.

🩺 Advisors

  • Hundreds of rules across the advisors — architecture, security, Hibernate, memory, dependencies
  • Every finding names the cause and the fix
  • One score out of 100, on demand — never on page load

📊 A live console

  • From the Scorecard to Runtime Insights and Java-agent instrumentation
  • Requests, SQL, transactions, exceptions and the JVM — as they happen
  • Explains what it means first; raw JSON one click away
02

One console, three stacks

Spring MVC, Spring WebFlux and Quarkus — from a single codebase.

Part 02 · The product

🖥️ Add a dependency. Open /bootui.

Spring Boot 4 — servlet or WebFlux

<dependency> <groupId>com.julien-dubois.bootui</groupId> <artifactId>bootui-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency>
The same starter for MVC and WebFlux; your app supplies its web stack.

Quarkus

<dependency> <groupId>com.julien-dubois.bootui</groupId> <artifactId>bootui-quarkus</artifactId> <version>2.2.0</version> </dependency>
A real Quarkus extension — runtime + deployment.
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
80+  Good
50+  Needs attention
<50  At risk
ArchitectureREST APISpring QuarkusDatabase 48Hibernate MemorySecurityPentestingVulnerabilities

📡 Live Activity — and the per-request profiler

One journal-backed feed, 17 signal types

RequestsSQLExceptions SecurityEmailsScheduled MessagingREST clientCache Fault tolerance TransactionsAILogs App eventsWebSocketsORM Async handoffs
  • Signals nest under the request that caused them
  • 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 id exact Stamped when the signal was recorded
Propagated exact Executor work followed by the optional Java agent
Trace id exact A trace id identifying one captured request
Serving thread exact The one worker that served it
Time window approximate Method, path and window
  • It degrades gracefully and never fabricates a correlation

🔎 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

🔬 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
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
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
  • WebFlux reads reactive SecurityWebFilterChain beans, non-blocking
  • 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 HandlerExceptionResolver and 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

OpenDefault for every new group
AcknowledgedSeen — keeps accumulating, never auto-transitions
ResolvedBelieved 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
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.
Fallback · Beat 1 Understand an unfamiliar app — Beans, focused dependency neighbourhood
BootUI Beans panel showing a focused dependency graph around sampleController
Focus a bean and see only its neighbourhood — dependencies, dependents, scope, and why this bean exists. Not a 400-node hairball.
Fallback · Beat 2 Run all scanners — the score appears from nothing
BootUI Scorecard showing known findings and advisor assessment coverage
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 3a Live Activity — vital signs, and a live service map
BootUI Live Activity panel with summary tiles and a live flow service map
The map is assembled from evidence BootUI already retained — opening it contacts nothing and probes nothing.
Fallback · Beat 3b SQL Trace — the N+1, down to the call site
BootUI SQL Trace panel with statement rankings and a possible N+1 badge
possible N+1 on a normalized statement — with OrderService named as the call site, and not one bound parameter on screen.
Fallback · Exceptions Exceptions — resolved, then thrown again
BootUI Exceptions panel with grouped exceptions and triage controls
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 4 Runtime Insights — evidence, changes and coverage
BootUI Runtime Insights with route timing evidence and performance deep dives
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

Point an agent at it

{ "servers": { "bootui": { "type": "http", "url": "http://127.0.0.1:8080/bootui/api/mcp" } } }
  • 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

Scanadvisors, with compact summaries and cached report details
Diagnoseactivity, request profiles, exceptions, SQL and traces
CompareRuntime Insights, change impact and retained runs
VerifyJava-agent status, Code Inventory, Code Paths and side effects
Controlbounded actions, sensor switches and method probes
Tools whose panel is absent are simply not advertised.
1 shared registry · loopback endpoint
0 screenshots to interpret

⌨️ 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

🔁 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
GitHub

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
  • Apache 2.0
  • Maven Central
  • Spring Boot 4 · Quarkus 3 LTS · Java 17
GitHub

Find my conference talks

Scan the QR code for current talks, slides and workshop information.

julien-dubois.com/conferences
Scan to open Julien Dubois's conference talks page