BootUI
Try it
Setup
Features
Workshop
Properties
AI agents
Ecosystem
GitHub
Try it
Setup
Features
Workshop
Properties
AI agents
Ecosystem
GitHub
  • Get started

    • Try the sample app
    • Setup
    • Spring WebFlux
    • Quarkus
    • Java agent
    • Activation and safety
    • Non-standard runtimes
    • Troubleshooting
  • Workshop

    • Workshop overview
    • 00 - Runtime understanding
    • 01 - Setup and tooling
    • 02 - Configuration and wiring
    • 03 - Runtime journal
    • 04 - Runtime Insights
    • 05 - Java instrumentation
    • 06 - Assessment and impact
    • 07 - Agent change loop
    • 08 - Going further
    • Participant worksheet
    • A - Prompts
    • B - Troubleshooting
    • C - Frameworks
    • D - Extensions
  • Features

    • All features
    • Scorecard
    • Live Activity
    • Runtime Insights
    • Advisors
    • Runtime
    • Configuration
    • Database
    • Security
    • Services
    • Diagnostics
    • Java Agent
    • Developer tools
  • Reference

    • Properties
    • Framework support
    • AI agents
    • Command line
    • BootUI family
    • Known limitations
  • Diagnostic checks

    • Architecture
    • REST API
    • Spring
    • Hibernate
    • Database
    • Security
    • Vulnerabilities
    • Memory
    • Pentesting
    • GraalVM readiness
    • CRaC readiness
    • Quarkus
    • Quarkus security
  • Contributing

    • Repository
    • Specification
    • Implementation plan
    • v2 plan
    • Try 2.0 early
    • v2 validation report
    • Releasing 2.0
    • Quarkus design notes
    • WebFlux design notes
  • Privacy

BootUI Specification

1. Overview

BootUI is a local-only developer console that adds an embedded, safe introspection and explanation layer to a running application. It runs on Spring Boot (servlet or WebFlux) and Quarkus from a single codebase: each stack ships a thin adapter — one Spring Boot starter (bootui-spring-boot-starter, for servlet and WebFlux alike) or a Quarkus extension — over a shared, framework-neutral engine, so all three serve the same Vue UI and the same /bootui/api/** REST contract. It is inspired by Quarkus Dev UI, .NET Aspire Dashboard, Laravel Telescope, Micronaut Control Panel, and Spring Boot Admin, but is focused specifically on the inner development loop of a single application.

BootUI is not a standalone application, production monitoring tool, APM product, cloud service, IDE plugin, or replacement for Actuator. It is a framework-native visualization and explanation layer loaded into the user's running application through a starter (Spring Boot) or extension (Quarkus) dependency.

Product descriptions, taglines, and directory listings use "Spring Boot and Quarkus" without version numbers. Explicit versions belong in compatibility requirements, dependency baselines, migration guidance, and historical notes.

1.1 Target platform

BootUI currently targets:

  • Spring Boot 4.x and Quarkus 3.x, from one shared, framework-neutral codebase.
  • Java 17 or later.
  • Maven-based applications first.
  • Spring Boot servlet web applications, Spring Boot WebFlux (reactive) applications, and Quarkus (Vert.x / RESTEasy Reactive) applications.

Maturity is stated honestly: the Spring Boot servlet adapter is complete (all panels). The Spring Boot WebFlux adapter reuses the same engine and serves the large majority of panels unmodified or over a rebuilt reactive capture layer, including Live Activity (all ten panel-buffer signal types merge identically to the servlet adapter — see docs/WEBFLUX-SUPPORT.md §6.4), the raw Spring Security panel, the WebFlux-native 26-rule Security advisor, and REST Client capture over instrumented WebClient instances; HTTP Sessions is not applicable to a reactive, container-session-free stack — see docs/WEBFLUX-SUPPORT.md for the current per-panel status. The Quarkus adapter serves most panels over the same engine; panels with no Quarkus equivalent (such as Conditions, Startup Timeline, Spring Data, Spring Security, Spring DevTools, GraalVM, and CRaC) are reported not applicable and JMS is not yet available — see docs/QUARKUS-SUPPORT.md for the current per-panel status.

Out of scope for the current release line:

  • Spring Boot 3.x compatibility.
  • Spring Framework 6 / Boot 3 compatibility shims.
  • A dedicated BootUI Gradle plugin (the Spring starters and Quarkus extension are consumable from Maven or Gradle as ordinary dependencies).

2. Product goals

2.1 Primary goal

Make a running Spring Boot or Quarkus application understandable in minutes.

2.2 Secondary goals

  • Reduce time spent debugging auto-configuration and configuration issues.
  • Help new developers onboard onto unfamiliar Spring Boot services.
  • Provide an IDE-agnostic UI for runtime Spring Boot insight.
  • Make Actuator data readable and actionable during local development.
  • Create an extensible platform where Spring ecosystem libraries can add BootUI panels.

2.3 Non-goals for MVP

  • Production monitoring.
  • Multi-application runtime orchestration. BootUI captures host-application traces locally and accepts OTLP traces from cooperating local services as a dev-time sink, but it does not run, schedule, restart, or supervise other processes the way .NET Aspire's AppHost does.
  • Kubernetes workflow management.
  • Hosted dashboards.
  • User accounts, roles, and identity-provider integration. BootUI uses one lightweight bearer token for API access from untrusted sources; it is not an application user-management system.
  • Full APM/tracing replacement. BootUI's telemetry capture is dev-only, bounded in memory, and never forwards data anywhere.
  • Upgrade automation.
  • Code editing.
  • Replacing Spring Boot Admin.

3. Target users

PersonaNeedsBootUI value
Solo/backend developerUnderstand a project quickly, configure it correctly, inspect endpointsOne local URL with beans, config, mappings, health, and logs
Enterprise service onboarderUnderstand inherited profiles, conditional beans, and dependenciesExplains effective runtime state without reading the whole codebase first
Platform engineerStandardize inspection across many Spring Boot servicesCommon diagnostic surface for all teams
Microservices developerDebug local service wiring and environment issuesShows local service health, connection details, mappings, and config sources

4. User experience

4.1 Activation

BootUI activates only in development contexts.

Default activation rules:

  • Enabled when the bootui-spring-boot-starter dependency is present in a Spring Boot application and at least one of these is true:
    • spring-boot-devtools is present.
    • Active profile is dev or local.
    • bootui.enabled=ON.
  • Disabled when:
    • bootui.enabled=OFF.
    • Active profile is prod or production, unless bootui.enabled=ON is set.

bootui.enabled=AUTO is the default. BootUI must fail closed: if no enabled profile is active and DevTools is not on the classpath, it should not expose the UI.

Failing closed covers the packaged console itself, not only its API. When BootUI is inactive no controller, resource handler, or safety filter is registered, but bootui-ui still ships the compiled shell on the classpath at META-INF/resources/bootui/, which every supported runtime serves from its own built-in static-resource handling. Each adapter therefore keeps a guard that answers 404 for the reserved internal /bootui namespace while the console is off: BootUiShellGuardAutoConfiguration on Spring MVC and Spring WebFlux (registered under the exact negation of the activation condition, and only when the packaged shell is actually on the classpath), BootUiProdShellGuardFilter on Quarkus (always registered, active in LaunchMode.NORMAL). A custom bootui.path moves the console's routes but never the classpath location of the bundle.

The URL that reaches that classpath location is not fixed, so the Spring guard resolves it rather than assuming it. It matches the decoded application path, the same form the framework's handler mapping resolves against, so encoded and matrix-parameter spellings such as /%62ootui/index.html cannot slip past it. It also follows Spring Boot's relocatable static handling — spring.mvc.servlet.path, spring.mvc.static-path-pattern, and spring.webflux.static-path-pattern — under which the same bundle surfaces at, for example, /app/static/bootui/index.html. A prefix is only claimed when the configured pattern can actually reach a nested resource, so a single-segment pattern such as /resources/* never costs the host a namespace it could not have exposed. Requests outside the reserved /bootui namespace and its derived prefixes are passed through untouched; the namespace itself is reserved, so a host application must not serve its own routes there. Republishing BootUI's classpath directory under a URL of the host's own choosing (by pointing spring.web.resources.static-locations inside it) is host-controlled and unsupported.

When BootUI is active, the starter should contribute low-precedence Actuator defaults for the local panels, including beans, conditions, configprops, env, loggers, mappings, metrics, startup, and scheduledtasks. Host applications can override those management.* settings explicitly.

On Spring, BootUI's panels are served by the application's own Spring MVC or Spring WebFlux stack; the starter brings neither. BootUI also supports non-web (command-line) applications that have Spring MVC and an embedded servlet container on the classpath: when BootUI is active and the host is configured as non-web (spring.main.web-application-type=none), the starter forces a servlet web application so the console can be served. This only happens while BootUI is active (development contexts by default), never overrides an explicitly reactive application, never runs without Spring MVC's DispatcherServlet and an embedded servlet container (Tomcat, Jetty, or Undertow) on the classpath, and never touches Spring Cloud's transient non-web bootstrap context (detected via its "bootstrap" marker property source) so Spring Cloud Config apps still start. It can be disabled with bootui.force-web=false.

4.2 URL

Default UI URL inside the host Spring Boot or Quarkus application:

http://localhost:<application-port>/bootui

The application port is server.port on Spring and quarkus.http.port on Quarkus.

/bootui is the backward-compatible default, not a fixed mount. Set bootui.path to move the shell, assets, APIs, filters, streams, downloads, action routes, MCP bridge, startup banner, and SPA runtime base together:

bootui.path=/dev-console

The API then defaults to /dev-console/api. bootui.api-path may move it independently when a host needs separate UI and API mounts:

bootui.path=/dev-console
bootui.api-path=/internal/bootui-api

Both values are application-relative. Spring's server.servlet.context-path or spring.webflux.base-path, and Quarkus' quarkus.http.root-path, are prepended exactly once. For example, bootui.path=/dev-console with an application root of /host is served at /host/dev-console; the generated shell injects that browser-visible base and API path for the shared Vue application.

The generated shell also injects the normalized, same-origin host-application path used by the Scorecard panel's Application homepage link. It is / at the default application root and includes exactly one trailing slash for a custom root (for example, /host/). The link therefore returns to / from /bootui/ and to /host/ from /host/dev-console/, independently of bootui.path depth and bootui.api-path, without carrying BootUI query or hash state.

Path configuration is normalized once and then used by every adapter:

  • leading/trailing whitespace and one or more trailing slashes are removed;
  • the value must be an absolute, non-root path made only of RFC 3986 unreserved segment characters (A-Z, a-z, 0-9, ., _, ~, -);
  • empty segments, . / .. segments, query or fragment content, percent encoding, backslashes, route-pattern characters, and other ambiguous values are rejected with a startup configuration error;
  • /bootui/** is reserved as the Quarkus adapter's internal classpath mount, so a custom UI path cannot be nested under it (the exact default /bootui remains valid). bootui.api-path may use /bootui/api, its backward-compatible default.

When a custom UI path is active, the packaged internal /bootui shell/assets/API mount is not publicly exposed. Quarkus reroutes the configured public paths internally without redirecting the browser, preserves query strings, and uses a per-request loop marker so rerouting cannot recurse. All adapters serve both the bare and trailing-slash shell URL directly, without a redirect loop.

4.2.1 Browser response-header policy

Every active adapter applies the same framework-neutral baseline to the configured UI and API surfaces (the defaults are /bootui, /bootui/**, and /bootui/api/**), including shell and asset responses, API errors, localhost/panel-policy rejections, SSE streams, and downloads:

HeaderBootUI baseline
Content-Security-Policydefault-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'
X-Content-Type-Optionsnosniff
X-Frame-OptionsDENY
Referrer-Policystrict-origin-when-cross-origin
Permissions-PolicyDisables accelerometer, camera, geolocation, gyroscope, magnetometer, microphone, payment, and USB access.

The CSP deliberately excludes unsafe-eval. style-src 'unsafe-inline' is currently required by Bootstrap/Vue style attributes, while data: is limited to images and fonts for the generated icon assets. base-uri 'self' permits only the same-origin <base> tag BootUI injects when a host application has a servlet context path or Quarkus root path.

BootUI treats cache semantics separately by response class:

  • the configured API path and its descendants, including JSON, errors, SSE, and downloads: Cache-Control: no-store, must-revalidate plus Pragma: no-cache.
  • successfully served content-hashed files under the configured UI path's assets/ directory: Cache-Control: public, max-age=31536000, immutable, with no conflicting Pragma.
  • The SPA shell, favicon, unhashed/missing assets, and other BootUI responses: Cache-Control: no-cache plus Pragma: no-cache.

Cache directives are owned by BootUI because they are part of each response class's safety contract. The other headers are baseline defaults: an existing host-provided value is preserved, and a host filter that runs later may replace the baseline with its own stronger policy. Headers are set rather than appended, so the clean adapter path emits one value per policy header.

Quarkus' production-dark 404 responses carry the same baseline and path-appropriate cache directive without exposing the shell or API. The Vite development server remains compatible because it serves the development document and HMR client itself; only proxied backend API responses carry these headers, and response CSP headers do not set the policy of the Vite document.

4.3 Startup banner integration

When BootUI is enabled, the application startup output should include:

BootUI is available at http://localhost:8080/bootui

On Spring the scheme follows server.ssl.enabled: when TLS is enabled the banner uses https:// instead of http://. The port and context path are resolved from local.server.port (falling back to server.port, then 8080) and server.servlet.context-path (spring.webflux.base-path on WebFlux), followed by the normalized bootui.path.

The Quarkus adapter logs the same line at startup, gated by the same bootui.show-banner key. Because the console is a local developer tool there, the Quarkus banner always uses http://; the port is the live bound HTTP port (quarkus.http.test-port under tests, otherwise quarkus.http.port) and the path is quarkus.http.root-path plus the normalized bootui.path.

This should integrate with the project's startup banner convention.

4.4 First-run experience

The first screen should show:

  • Application name.
  • Framework name and version (Spring Boot or Quarkus).
  • Java version.
  • Active profiles.
  • Server port and management port.
  • BootUI safety status: local-only, dev mode, enabled reason.
  • Quick links to the main panels.
  • Warnings for missing recommended data sources, such as Actuator endpoints not available.

4.5 Expensive action admission

Explicit expensive scans use one framework-neutral single-flight admission per scanner/service instance. Architecture, REST API, Spring/Quarkus application, Database, Hibernate, Memory, Security, Pentesting, GraalVM, CRaC, and Vulnerabilities/OSV scans, and the PostgreSQL and MySQL reads, are protected independently; unrelated scanners can still run concurrently. Heap Dump capture, analysis, and delete share one admission because they operate on the same files, histogram, and status.

A duplicate request never waits or repeats the work. MVC, WebFlux, and Quarkus return 409 Conflict with the same JSON shape:

{
  "error": "BootUI action already in progress",
  "operation": "architecture.scan",
  "activeOperation": "architecture.scan",
  "message": "Operation 'architecture.scan' cannot start while 'architecture.scan' is in progress."
}

Operation ids are stable <panel>.<action> values. Passive GET requests continue returning the last completed report while an action runs; a rejected duplicate does not mutate cached reports, timestamps, Heap Dump state, GraalVM progress, or Memory trend samples. Activation, localhost/Host/cross-site-write safety, panel enabled/read-only policy, validation, confirmation, and feature configuration are evaluated before single-flight admission. In particular, bootui.vulnerabilities.osv-enabled=false still returns its existing DISABLED report without claiming admission or performing network work. The shared UI treats this conflict as a warning, retains the visible report/Scorecard score, and stops only the duplicate caller's spinner.

5. Functional specification

5.1 Scorecard panel

The Scorecard keeps the overview panel id and its bootui.panels.overview.* properties. Its route is #/scorecard, and #/overview redirects there.

Purpose: give a fast summary of the running application.

Data:

  • Application name.
  • Framework name and version (Spring Boot or Quarkus).
  • Java version.
  • JVM vendor and runtime.
  • Active profiles.
  • Web application type.
  • Server port.
  • Management port.
  • Context path.
  • Startup duration if available.
  • BootUI activation reason.

Acceptance criteria:

  • Shows a useful overview without requiring any configuration beyond adding the starter.
  • Clearly marks missing optional data as unavailable, not as an error.
  • Does not expose environment secrets.

5.1.1 GitHub

Purpose: summarize the current repository's GitHub project state from the local git origin, in the Developer tools group.

Data sources:

  • Local .git metadata for repository detection and branch/upstream information.
  • GitHub REST API during the panel's one-minute auto-refresh/manual refresh cycle.
  • Local credentials from GITHUB_TOKEN, GH_TOKEN, or gh auth token, never persisted or returned to the browser.

Features:

  • Show repository identity, visibility, default branch, local branch, stars, forks, watchers, and recent push time.
  • Show bounded open pull requests, issue buckets and the bounded open issue list, latest GitHub Actions executions, configured workflow links, and permission-aware security signals.
  • Render every resource returned by /rate_limit dynamically, plus best-effort repository/owner quota cards when authorized.
  • Skip optional sections when remaining core API quota is at or below the safety threshold.

Acceptance criteria:

  • Initial page load and /bootui/api/panels do not spawn subprocesses or call GitHub.
  • Outbound GitHub calls happen only through POST /bootui/api/github/refresh and are blocked by read-only settings.
  • The quotas/rate-limits drawer is hidden by default and highlights resources at quota or with 10% or less remaining.
  • Private repositories, missing credentials, rate limits, denied quota endpoints, and unsupported GitHub Enterprise endpoints produce stable unavailable states instead of raw errors.
  • Tokens, authorization headers, response bodies, and subprocess stderr are never exposed to the browser.

5.2 Beans Explorer

Purpose: answer "Which beans exist, and where did they come from?" and "How is a given bean wired into the application?"

Data sources:

  • Actuator beans endpoint (Spring Boot); Arc/CDI BeanManager (Quarkus).
  • Actuator conditions endpoint for exact positive auto-configuration evidence when a Spring bean resource identifies its configuration class.
  • Spring application context.
  • Optional internal BootUI metadata for auto-configured vs user-defined beans.

Features:

  • Search by bean name, class name, package, scope, and resource.
  • Provide a clearly labeled Graph/List segmented control, with Graph selected by default.
  • On first open, select the Application bean with the most direct dependencies and dependents, break ties alphabetically, and center its graph node in the bounded scroll viewport. Leave isolated inventories unfocused.
  • Hide BootUI's own beans by default so the report focuses on the host application; bootui.monitoring.exclude-self=false includes them when debugging BootUI itself. Omit the BootUI classification option when the loaded inventory contains no BootUI beans.
  • Filter by current classification:
    • application beans.
    • BootUI beans, when self-data filtering is disabled.
    • Spring framework beans.
    • Java/Jakarta platform beans.
    • other beans.
  • Show bean name, type, scope, resource/declaring class when available, dependencies, aliases, and classification.
  • Dependency graph mode (toggle in the panel header):
    • Is the panel's default view; the server-paged list remains available from the header toggle and loads only when selected.
    • Each bean name in the list is a keyboard-accessible action that opens its focused graph and selects the bean's classification.
    • Loads beans in bounded 1 000-row pages, up to a 2 000-bean client-side inventory. When more beans exist, the UI reports the loaded and total counts instead of implying that the graph is complete.
    • A search field selects the focus bean by name, alias, or a unique type match. Application beans are the default classification; the operator can select another classification or all beans. The classification applies to focus choices and rendered neighbours, so the Application graph contains only host-application beans.
    • Renders a concentric-ring SVG neighbourhood centred on the focus bean: direct dependencies (focus → node), direct dependents (node → focus), mutual/cycle nodes, and deeper-hop nodes up to depth 3 and 60 nodes total.
    • Provides keyboard-accessible zoom-out, reset, and zoom-in controls from 60% to 200%; the bounded graph region scrolls when the scaled SVG exceeds its viewport.
    • Nodes are colour-coded by role (focus, dependency, dependent, mutual, deep) in both light and dark themes at WCAG 2.1 AA contrast.
    • Clicking any node navigates the graph to that bean's neighbourhood.
    • When the 60-node or depth-3 limit is reached, a notice identifies which bound was reached.
    • Cycle-safe: a visited-set prevents infinite BFS traversal on any dependency cycle.
    • Duplicate bean names are represented by one deterministic node with the definitions' dependencies merged; focusing that node explains the ambiguity rather than silently discarding a definition.
    • The focused-bean details area shows type, scope, resource, aliases, definition count, and direct relationship counts. For an exact classpath-resource match, it queries the existing bounded positive Conditions endpoint and shows only evidence whose source is that configuration class or one of its methods. Missing resources, no exact match, disabled Conditions, and request failures remain explicit non-evidence states; provenance is never inferred.
    • Graph and list data paths remain independent: the default graph does not fetch the server-paged list, and the list loads only when selected.
    • Keyboard-navigable: one graph tab stop, arrow/Home/End movement between SVG nodes, Enter/Space to focus, visible focus rings, and an aria-label with the full bean name.
    • Reduced-motion safe: the graph uses a deterministic static layout with no motion or transition.
    • Quarkus capture: capture Arc's resolved injection edges during augmentation and overlay them on the live CDI inventory through the same BeanSummary.dependencies contract. Defining resources and Spring Boot Conditions evidence remain explicitly unavailable.

Acceptance criteria:

  • A developer can find a bean by name or type.
  • A developer can inspect why a bean matters by seeing dependencies.
  • Large applications remain usable through search and lazy loading.
  • A developer can focus any bean and explore its dependency neighbourhood by clicking through nodes.
  • Cycles, isolated beans, depth limits, node limits, and missing dependency data each produce a deterministic, explained result rather than an error or blank state.
  • The existing list/filter/paging behaviour remains available after switching from the default graph view.

5.3 Conditions Explorer

Purpose: answer "Why was this auto-configuration applied or skipped?"

Data sources:

  • Actuator conditions endpoint.
  • Spring Boot condition evaluation report.

Features:

  • Group by auto-configuration class.
  • Show positive matches.
  • Show negative matches.
  • Show unconditional classes.
  • Search by class, condition type, missing class, missing bean, missing property.

Acceptance criteria:

  • Raw condition messages are preserved.
  • BootUI presents Spring Boot condition messages without binding the browser to raw Actuator JSON.
  • Negative matches are easy to discover.

5.4 Configuration Properties Explorer

Purpose: answer "Which Spring Boot configuration properties exist, which values are active, where did they come from, and can I modify them safely during local development?"

Data sources:

  • Actuator env endpoint.
  • Spring Boot configuration metadata.
  • Environment property sources.

Features:

  • List and search all Spring Boot configuration properties visible to the application.
  • Match property names through relaxed binding, so a dotted query also finds a property supplied as an UPPER_SNAKE_CASE environment variable, which every property source enumerates under its literal name.
  • Show effective value.
  • Show source property source.
  • Show active profiles.
  • Show known default where metadata is available.
  • Show description from configuration metadata where available.
  • Suggest known Spring Boot configuration keys when creating an override.
  • Detect and mask likely secrets:
    • password
    • secret
    • token
    • key
    • credential
    • private
  • Modify configuration properties through local runtime overrides.
  • Add a runtime-only override for an existing property.
  • Add a runtime-only override for a new property key.
  • Edit a runtime override.
  • Remove a runtime override.
  • Require explicit confirmation before creating, updating, or removing an override file entry.
  • Show whether a displayed value comes from a BootUI runtime override.
  • Clearly label modified values as local, runtime-only, and not persisted to application.properties, environment variables, or config server.
  • Persist overrides to BootUI's override file by default so they can be reapplied on restart.
  • Explain when a modified property may not affect already-created beans or already-bound @ConfigurationProperties without restart or explicit rebind support.

Acceptance criteria:

  • Secret-like values are masked by default.
  • The UI explains where an effective value came from.
  • Unknown properties are still searchable.
  • Custom @ConfigurationProperties metadata is displayed when available.
  • Developers can create, update, and remove local runtime overrides for Spring Boot configuration properties.
  • BootUI never writes secrets or modified values back to source files by default.
  • Every property mutation is local-development-only and is persisted only to BootUI's override file, .bootui/application-bootui.properties by default.
  • The UI does not write the override file until the developer explicitly confirms the pending create, update, or remove action.
  • Mutating a property returns a clear result that states whether the new value is visible in the Spring Environment and whether restart/rebind may be required.

5.5 Mappings Browser

Purpose: answer "Which HTTP endpoints does this app expose?"

Data sources:

  • Actuator mappings endpoint.

Features:

  • List HTTP mappings by method and path.
  • Show handler class and method.
  • Show consumes/produces metadata.
  • Search paths and handler names.

Acceptance criteria:

  • All Spring MVC or WebFlux routes appear when available.
  • The UI handles apps with no web layer gracefully.
  • Unsafe methods are not automatically called.

5.6 Health Dashboard

Purpose: answer "What dependency or component is unhealthy?"

Data sources:

  • Actuator health endpoint.

Features:

  • Render health tree.
  • Show status at each level.
  • Highlight failing contributors.
  • Show details when available.
  • Explain when details are hidden by Actuator configuration.
  • Show a disabled state with setup guidance when the Actuator health endpoint is not available.
  • Show guidance, without changing reported statuses, when a tree is made only of Spring Boot default health indicators.

Acceptance criteria:

  • Overall status is visible immediately.
  • Failing health contributors are easy to identify.
  • The UI does not require production-style health exposure.

5.6.1 HTTP Sessions Panel

Purpose: inspect and act on local servlet HTTP sessions without exposing bearer session identifiers by default.

Data sources:

  • Embedded Tomcat Manager instances discovered from the live TomcatWebServer.

Features:

  • List at most bootui.http-sessions.max-sessions sessions, defaulting to 50, with creation time, last access time, idle duration, validity, current-session marker, and attribute count.
  • Use an opaque server-derived session key for actions and a masked display id unless bootui.expose-values=FULL.
  • Show attribute names, types, and masked values by default; METADATA_ONLY hides values, and FULL reveals stringified local values with bounded length.
  • Clear all attributes from a selected session after explicit confirmation.
  • Destroy a selected session after explicit confirmation.
  • Report a stable unavailable state when the app is not running on embedded Tomcat or no live session manager exists.

Acceptance criteria:

  • Session ids are not returned to the browser unless full value exposure is explicitly enabled.
  • Actions are blocked by global read-only mode and bootui.panels.http-sessions.read-only.
  • The session list is bounded by default and reports when more sessions exist than are returned.

5.7 Logger Controls

Purpose: answer "Can I inspect and change log levels without restart?"

Data sources:

  • Actuator loggers endpoint.

Features:

  • Search loggers.
  • Show configured level and effective level.
  • Set level at runtime.
  • Clear configured level.

Acceptance criteria:

  • Runtime level changes work when Actuator supports them.
  • Level changes apply at runtime only and are not persisted.

5.7.1 Spring DevTools Controls

Purpose: answer "Can I trigger local LiveReload or restart this app from the console?"

Data sources:

  • Spring Boot DevTools restart APIs when present and initialized.
  • Spring Boot DevTools LiveReload server when present.

Features:

  • Show whether DevTools restart is available.
  • Show whether LiveReload is available and which port it uses when reported.
  • Trigger a LiveReload notification for connected browsers.
  • Restart the local application through DevTools after explicit confirmation.
  • Poll for the application to return after a restart is scheduled.
  • Explain unavailable states instead of hiding the panel.

Acceptance criteria:

  • DevTools is optional; BootUI must still start when DevTools is absent.
  • Restart actions require explicit confirmation and are intended for local development only.
  • Restart scheduling returns an API response before DevTools tears down the running context.
  • LiveReload is clearly described as a notification to connected browser tooling, not a forced BootUI page reload.

5.7.2 Java Agent

Purpose: answer "Is BootUI's optional Java agent attached and claimed, and how do I attach it?"

Data sources:

  • The bootstrap-loaded io.github.jdubois.bootui.agent.bridge.AgentBridge, found only through the bootstrap class loader.
  • Adapter claim state from Spring or Quarkus.
  • The local Maven repository path, honoring maven.repo.local, and build-tool markers in the working directory.

Features:

  • Report NOT_ATTACHED, DORMANT, ARMED, HELD, DISARMED, UNAVAILABLE, FAILED, or DISABLED, with a reason and heldBy when another application owns the JVM-wide agent.
  • Show BootUI and agent versions, protocol and expected protocol, JDK, load mode, jar path, startup time, claim details, sensors, retransformation status, counters, messages, warnings, and setup snippets. The JavaAgentReport fields are state, reason, agentVersion, bootUiVersion, protocol, expectedProtocol, jdk, loadMode, jarPath, startupMicros, claim, heldBy, sensors, retransformation, counters, messages, warnings, and setup.
  • Show each installed sensor's state, hooks, self-test, transformed types, and available counters. Executor/thread propagation, inventory, and code-paths sensors expose their dedicated counter records; Side Effects and caught exceptions expose their detailed observations through their own panels. When none is installed, say: "No sensor installed: the agent installs the sensors this application asks for when it claims the agent (bootui.agent.sensors)."
  • Bound executor and started-thread propagation independently: each weak identity registry retains at most 32,768 application entries across all claim generations plus 64 reserved self-test entries, with a separate 32,768-entry current-claim admission cap. Earlier-generation entries remain until consumed or reclaimed so shared tasks stay ambiguous; disabling the thread sensor clears its pending entries. pending is the current retained-entry count; overflow and neverApplied are cumulative since agent startup and survive a runtime sensor disable/re-enable. Overflow means a submission was not recorded, not that every later execution is certainly unowned: if the same task object is admitted later, the earlier execution can take its snapshot. The Java Agent panel warns about that historical shared-task ambiguity.
  • While the state is NOT_ATTACHED, open on the setup snippets with the steps to attach the agent, followed by what a Java agent is, which features need it, what works without it, and its cost; leave out the sections that describe an attached agent. Every other state shows the agent's diagnosis first and the setup snippets last.
  • Offer copyable setup snippets for curl, Maven and Gradle download (curl-download, maven-download, gradle-kotlin-download, gradle-groovy-download), Spring Boot Maven plugin agents (maven-plugin), Gradle Kotlin/Groovy bootRun (gradle-kotlin, gradle-groovy), Quarkus dev mode -Djvm.args (quarkus-dev), Surefire/Failsafe @{argLine} (surefire) for JaCoCo coexistence, IntelliJ VM options (intellij), and JAVA_TOOL_OPTIONS (java-tool-options), with detected build-tool snippets first.
  • Report UNAVAILABLE when the agent version differs from BootUI (an agent only works with its own version) and explain the expected HotSpot CDS warning caused by appending the agent to the bootstrap class path.

Acceptance criteria:

  • The panel is always available on Spring MVC, Spring WebFlux, and Quarkus. Its runtime switch actions can turn (threads, files, environment, thread-activity, thread-locals, security-sinks) on or off while this application's claim is armed. The browser panel uses the Java Agent panel; MCP exposes enable_agent_sensor and disable_agent_sensor, which agents call only with the user's approval. All paths are refused by bootui.panels.java-agent.read-only and bootui.read-only. The override lasts only until the JVM ends and is not persisted; security-sinks request-value matching still requires bootui.agent.security-sinks.request-values=true at startup.
  • GET /bootui/api/java-agent, get_agent_status, and bootui agent status return the same JavaAgentReport.
  • Spring claims from BootUiAgentClaimEnvironmentPostProcessor (registered in META-INF/spring.factories) once BootUI activation is resolved, refines after context refresh, disarms on close or startup failure, and releases the agent when BootUI or bootui.agent.enabled is off.
  • Quarkus claims from a STATIC_INIT recorder in dev/test, refines on startup, disarms on shutdown, and reports DISABLED in production launch mode.
  • The dependency inventory does not count a jar whose manifest carries BootUI-Agent-Protocol as an application library.

5.7.3 Code Inventory

Purpose: answer "Did the code I changed run, which application code did this run never execute, and which dependencies did it never load?"

Data sources:

  • The BootUI agent's inventory sensor through the bootstrap bridge: per-method hit flags for this run, which methods it tracks, first-call and first-class-of-a-jar records with their request, route, and time, and per-code-source class counts. The engine drains the agent's ring with a bootui-agent-drain daemon of the run.
  • A scan of the application's own class files in the claimed packages, off the request path once per run, hashing each method over its resolved instructions without debug attributes, bounded by bootui.code-inventory.max-classes and bootui.code-inventory.scan-timeout.
  • The previous run's method hashes, kept across DevTools restarts and Quarkus live reloads in the same JVM.
  • The application's declared dependencies from the Vulnerabilities panel's inventory.

Features:

  • A header "N of M application methods executed" for this run, where M counts the methods the agent tracked.
  • Changed since the previous run (first when a previous run exists): methods changed or added since the previous run, without git, each executed or not in this run, with the first request and route that ran it; removed methods as a count. While the scan runs, or after it failed, it says so and compares nothing (the change counts carry scanStatus).
  • Application code: packages and classes with executed, never-executed, and not-tracked counts, filterable to never executed. A method counts as tracked only when the agent instrumented its class in this run, or when its class has not loaded in this run at all. A method the agent could not see is not tracked, with its reason (static initializer, abstract method, a class the agent excludes by name, transform failed, over the method limit, ran before instrumentation), never counted as executed or never executed.
  • Dependencies: declared jars and the jars that defined classes, with classes loaded in this run, at startup or later, and the first route; a declared jar with no class loaded is not loaded in this run, never unused.
  • GET /bootui/api/code-inventory, /changes, /methods, and /dependencies; get_code_inventory and bootui code inventory take query (changed by default, never-executed, not-tracked, executed, dependencies, or a package, class, or method name) and limit.

Acceptance criteria:

  • The panel is view-only on Spring MVC, Spring WebFlux, and Quarkus; it is available while the agent's inventory sensor records this run and otherwise unavailable with the Java Agent panel's reason, and every read answers the same available: false shape.
  • Editing one method and letting DevTools restart lists exactly that method as changed, executed or not according to the requests sent.
  • A seeded never-called method is never executed, and a declared jar the sample never loads is not loaded in this run.

5.7.4 Code Paths

Purpose: answer "Which of my methods does this route spend its time in?"

Data sources:

  • The BootUI agent's code-paths sensor through the bootstrap bridge: per-thread fragments of each request's call tree of application bean methods, merged by the engine into request trees and, once settled, into one route tree per route and run (per node: requests, calls, total and self time, and a 32-bucket log2 histogram of per-request time with its least and most).
  • The runtime journal's http events, which name each request's route and outcome, and its SQL, REST client, cache, and AI events, which carry the code-paths stamp of the instrumented method innermost on their thread when they were recorded there (0 when recorded on another thread).
  • The Beans panel's beans and their declared dependencies, and the bean classes the claim asked the sensor to instrument, for Beans at runtime.
  • The adapters' marks of requests whose handler only assembled its result: a Spring MVC request that started async processing, and a Quarkus resource method on the event loop or returning Uni, Multi, CompletionStage, or a publisher, or one Quarkus could not identify, in a bounded lock-free set; Spring WebFlux marks every request once, for the whole stack.

Features:

  • Routes ranked by warm median, each with its warm requests, first recorded request, median, 95th percentile, and top methods by self time; assembly-only routes labelled (a handler on an event loop, returning a reactive or asynchronous result, or whose work BootUI could not place).
  • The selected route's tree as an indented table: method, calls per request, total, self, an approximate (≈) median from log2 buckets, share of the handler (of the request when no handler phase is known), and a share bar; asynchronous children marked and kept apart under the method that submitted them; an Other node per parent and phase past the budget (2,000 nodes a route, 100,000 and 500 routes a run). A node is its caller, method, and request phase, so handler and response-write time stay apart; a tree joins its route once its request's exchange is recorded, waiting up to the executor handoff window for a slow reactive response's.
  • The SQL, REST client, cache, and AI calls each method issued, per request with their time, under it: stamped where they were recorded with the innermost instrumented method open on their thread. A statement Hibernate flushes at commit runs after the @Transactional method returned, in the transaction interceptor around it (Spring's TransactionInterceptor, Quarkus's ArC interceptor), so it shows under the method that called the @Transactional one. Calls issued while no instrumented method was open (filters, response write) and calls recorded on another thread show under no method and are counted apart, each with why.
  • Selecting a method shows its callers within the tree and the routes that reach it.
  • Beans at runtime: the observed calls between beans with their counts beside the declared dependencies, with a filter for declared dependencies not called in this run, never called "unused", first requests included. A dependency is not called only when both beans' classes are instrumented and none of their methods was adaptively excluded; otherwise it is not observable, with why, and never counted as not called. An inherited method maps to the bean whose class extends its declaring class, preferring the one the caller declares. The runtime model gains observed INVOKES edges between beans, which change impact's code closure never walks.
  • The methods adaptively excluded in this run, with why.
  • route-time-breakdown splits a route's handler work into its top five handler-phase methods by own time (self time minus the recorded calls stamped to the method), the rest as other handler time, unless its tree is assembly only; the parts never exceed the handler work, and a limitation says how many recorded calls carried no stamp and stayed in their method's own time; the handler is not split when none of its calls carried a stamp, or when calls without a stamp take 10 % of its handler phase or more. repeated-selects names the method that issued the repeated statements: for a repository or DAO method of the application's own, the first method above it that is not one, through the repository method.
  • GET /bootui/api/code-paths, /code-paths/route?route= (paged by depth, offset, and limit), /code-paths/requests/{requestId}, and /code-paths/beans; get_code_paths and bootui code paths take query (a route, or part of a route or method) and limit.
  • Method probes, the panel's only actions: Probe this method on a selected tree method, or Probe in Code Paths from Code Inventory's changed methods, after a confirmation, retransforms that one method (binary.Class#name, with its descriptor for an overloaded one) and records its next 20 invocations, for at most 60 seconds, five probes at once: each invocation's duration, thread kind, request id, outcome (returned, or the thrown exception's type), and calling frame (the first frame of the application's packages above the method, past proxies and interceptors). Metadata only by default: never an argument or a return value. Started with Record argument and return shapes ("recordShapes": true), a probe also records the shapes of the first nine arguments, at entry, and of the return value: runtime types, nullness, and, by exact JDK class, collection, map, and array sizes and Optional presence, read without calling any application method; a string's length, a char[] or byte[] length, and an enum constant's name are shown under bootui.expose-values=FULL only, no shape under METADATA_ONLY, and MCP, the CLI, and exports never show one. The method must be one the agent's inventory or code-paths transformer saw, in the claimed packages; only the current run's copy of its class is advised (a probe on a class this run has not loaded yet waits for it, within its window). The agent enforces the bounds where the method runs, ends a probe with its run (a restart, a live reload, a disarm, or a release), and removes its advice by retransformation, keeping the inventory and code-paths advice; a removal it cannot confirm is reported. A method returning a reactive or asynchronous result is flagged: its durations time the assembly only. GET /bootui/api/code-paths/probes lists the run's probes with their invocations, POST /code-paths/probes with {"method": ..., "recordShapes": true} starts one (400 for a method that cannot be probed, 409 when refused: unavailable, five running, already probed, or shapes unavailable), GET /code-paths/probes/{id} reads one (404 when unknown), and POST /code-paths/probes/{id}/stop or DELETE /code-paths/probes/{id} stops one. start_method_probe (bootui probe start <method>) and get_method_probe (bootui probe show <id>) are the agent tools, with metadata only in every exposure mode.

Acceptance criteria:

  • The panel is available on Spring MVC, Spring WebFlux, and Quarkus while the agent's code-paths sensor records this run and otherwise unavailable with the Java Agent panel's reason, and every read answers the same available: false shape. Its reads start no scan, network call, or mutation; its method probes are actions that the global or per-panel read-only policy (bootui.panels.code-paths.read-only) refuses, in the API, MCP, and the CLI.
  • A probe never outlives its bound, and never records an argument or a return value; its shapes run no application method and never reach MCP, the CLI, or an export.
  • On the Spring sample with the agent, the seeded slow route's breakdown names SlowPricingService.quote, and the route tree's handler-phase time reconciles with the handler phase within 5 %; the seeded N+1 route's repeated-selects names InsightOrderService.ordersLineByLine, its statements show under that method, and Beans at runtime lists the controller's calls into it.

5.7.5 Side Effects

Purpose: answer "Which processes, hosts, and other side effects did this route or background work start or reach?"

Data sources:

  • The BootUI agent's side-effect bridge through the bootstrap loader. The processes, network, files, blocking, and resources sensors record by default, and environment, thread-activity, and thread-locals when opted in. security-sinks records, when opted in with bootui.agent.security-sinks.request-values=true, request input reaching SQL text, a command, a file path, or an outbound URL unchanged: the redacted sink, the parameter's name, and a sentence stating the fact; opted in alone, its JDK checks record deserialization without an ObjectInputFilter, weak MessageDigest and Cipher algorithms (application and library requests apart), the application's trust managers and known library trust-all trust managers, and default hostname verifiers and SSL socket factories.
  • Each request's end, which the adapters mark once its response is complete (Spring MVC once an async request's context completed, Spring WebFlux when the chain terminates, Quarkus when the response body ended), for the thread-activity sensor to check what the request left running and the resources sensor what it left open, each whether or not the other is on.
  • The runtime journal's REST client events, and the SQL Trace, messaging, and Email panels' availability, decide whether a panel captured a network connection's work.
  • The event loops each adapter registers with the agent's blocking sensor: Reactor Netty's on Spring WebFlux and for a WebClient, Vert.x's on Quarkus, each from the first request or response it handles there.
  • The runtime journal's HTTP exchange names the request route. If it is disabled, route rows merge under (route hidden: HTTP Exchanges is disabled) and expose no request ids.
  • The Code Paths stamp, when the Code Paths panel is enabled and the code-paths sensor is active, names the bean method that was innermost when the side effect happened. While Code Paths is disabled, rows lose that inside method and merge without it.

Features:

  • One Java agent panel after Code Inventory, view-only on Spring MVC, Spring WebFlux, and Quarkus. It is available while the BootUI agent is attached and armed for the application with a bridge that supports Side Effects. Otherwise it is unavailable with the Java Agent panel's reason, starting Requires the BootUI agent. Quarkus detector-gates it in dev and test mode like Code Paths.
  • Tabs by sensor group: Network, Files and processes, Environment, Threads and leaks, Blocking, and Security sinks.
  • The processes sensor hooks the JDK ProcessBuilder.start path used by ProcessBuilder.start(), ProcessBuilder.startPipeline(...), and Runtime.exec(...). It records the command name only: for a started process, its executable's file name after the last path separator of the first command element; for a failed start, that element up to its first whitespace or = first. An element that opens with a quote, as Windows passes "C:\Tools\app.exe" --token x verbatim to CreateProcess, keeps only its quoted text, and nothing past a quote inside the element is kept. Only letters, digits, ., _, +, and - are kept; other characters become ?, and the result is at most 128 characters. It never records arguments or environment, and also records start failure, exit status, and lifetime when watched. Starts are published at once; exits are attributed as their starts were and carry the start time. Exits are watched on the agent-owned bootui-agent-process-exits executor, with at most 1,024 watched at once.
  • The network sensor hooks Socket.connect(SocketAddress, int), SocketChannelImpl.connect, blockingConnect, and finishConnect, DatagramChannelImpl.send, DatagramSocket.send, and InetAddress.getAddressesFromNameService (a JVM address-cache miss). It records a host string and port, never resolved, without user information, sanitized, at most 128 characters and 1,024 distinct targets a run, or a looked-up name; connect, send, and resolution time; the outcome; and the client recognized in the engine, infrastructure first (an exporter's, shipper's, or container tool's frame, never OpenTelemetry's instrumentation; an exporter thread; a configured exporter endpoint; DNS), then from the first frame outside the socket plumbing and the call site's frames, then a well-known infrastructure port, then the thread family. It never reads a byte sent or received. Connects and lookups are published at once; a non-blocking connect is pending until its finishConnect record, which carries its owner and time; a datagram's first send per target and call site is published at once and the next ones counted in the thread's table.
  • A network connection or datagram row is captured (with the panel id), not-captured (no visible panel shows the work), or infrastructure (DNS resolvers, telemetry exporters, metrics and log shippers, container tooling). A JDBC, messaging, or mail client's is captured while SQL Trace, the broker's panel, or Email is available and enabled, decided on read. Any other is captured when a REST client call of the same request or execution, or, unowned, at the same time with a second of slack, names its host and port, or a configured proxy; it waits for that until its request was named plus 2 seconds (or the connect, when later), an execution's 60 seconds, unowned 10 seconds, 60 for a recognized HTTP client; a finish record is decided as its connect was. A datagram's owner comes from the thread's slot only, which a request scope fills even without code paths; an event loop never captures an owner. The runtime model gains observed OPENS edges from routes, scheduled jobs, and beans to HOST nodes keyed host:port, hidden with the panel; change impact never walks them.
  • The files sensor hooks FileInputStream, FileOutputStream, and RandomAccessFile's private open methods, the Files methods that open (newByteChannel, newInputStream, newOutputStream), delete, move, and copy, and FileChannel.open. The agent turns each path into a pattern before it leaves the hook: the working directory as ., the temporary directory as $TMPDIR, the home as ~, another user's home as *, ids collapsed to {token}, {uuid}, {hex}, {id} (12 or more characters mixing letters and digits, or 24 or more letters mixing cases), and {n}; the engine masks a segment its secret detector recognizes (a JWT, a PEM key, an AWS key, a credential URL). Class files, archives, archive file systems, Java's home, class path directories, and any other file a class loader reads are counted in buckets. Each row has a kind (read, write, delete, move from, move to, copy from, copy to), a location (working-directory, temporary-directory, home, system, elsewhere), and an origin (application, library, class-path, jdk, logging, unknown); class path, JDK, and logging rows are grouped apart. Never contents.
  • The opt-in environment sensor hooks System.getenv and System.getProperty at entry, records a name the first time a thread reads it for a request or execution, drops reads whose immediate caller is the JDK, and never records a value or a default.
  • The runtime model also gains FILE_PATTERN and ENVIRONMENT_VARIABLE nodes, with OPENS and READS edges from routes, GraphQL operations, and scheduled jobs, for application rows, while Side Effects is visible; change impact's closure and its route reads and writes ignore them.
  • BootUI's own process starts and connections (its JDK HttpClients run on a bootui-http-N executor), agent threads, BootUI threads, reentrant inner hooks, and work during class transformation are not recorded, nor is the JDK's own loopback pair. A hook that fails its self-test is left out and its sensor keeps recording; a failing core hook disables only its sensor, and the transformer is reinstalled with the others. After 100 internal side-effect-sensor errors, the sensors switch off for the JVM's life. The side-effect ring holds 1,024 records. File operations and environment reads in a request scope aggregate in the thread's table and flush when it ends.
  • Rows are keyed by attribution, sensor, kind, normalized target, call site, and bean method stamp when available. Attribution prefers request route, then the execution no request owns as its journal event names it (a scheduled run, a consumed message, a WebSocket message; work no request owns when never named), startup, thread family with digits collapsed, and finally unattributed. A request whose route is still unknown after 30 seconds counts under (unknown route). Inline work owned by another request remains attributed to that other request through a small per-thread owner stack. Targets normalize the home directory to ~, UUIDs to {uuid}, long hex runs with a digit to {hex}, and digit runs to {n}, except network targets, which keep their digits. The call site is the first frame in the application's packages, else the first frame outside the JDK.
  • Each row reports count, failed, completed, non-zero exits, last exit status, total and longest lifetime, first and last seen, and up to three exemplar request ids; a network row also its client, capture, and capturedBy. Rows are capped at 500 per sensor and 2,000 per run, with the rest counted in that sensor's Other row. Up to 10,000 observations wait for a route. Side Effects is an AgentEvidence store: Clear recording and Free BootUI memory clear its rows, records still in the agent with a first occurrence before the clear are dropped by a watermark, the panel says The recording was cleared, and journal status reports sideEffectRows and sideEffectsWaiting. These bounds shrink in proportion with a smaller bootui.runtime-journal.agent-evidence-max-bytes.
  • The Side Effects panel's own visibility gates all reads. A disabled Side Effects panel shows nothing and says The Side Effects panel is disabled.
  • GET /bootui/api/side-effects returns every sensor with tab, state, reason, rows, occurrences, dropped records, hooks, and limitations. GET /bootui/api/side-effects/sensor?sensor=<id>&offset=&limit= returns one sensor's rows, most frequent first, with a default limit of 100 and maximum of 500; an unknown sensor id is 400 with {error}. get_side_effects and bootui side-effects take query and limit (20 by default) and return every sensor's coverage followed by matching rows, most frequent first; query not captured lists the hidden outbound calls.

Acceptance criteria:

  • With the agent, the Spring sample route GET /api/side-effects/java-version shows a java process row attributed to the route and JavaVersionReporter#version; its extra -D... argument is never shown. The counterexample GET /api/side-effects/runtime-version starts no process and shows no row.
  • With the agent and side-effects-seed.scheduled-every set, the Quarkus sample's scheduled ScheduledJavaVersion#report run shows a java process row of scope execution, named as the runtime journal names that scheduled run, with no exemplar request.
  • The blocking sensor reports Thread.sleep, TimeUnit.sleep, Object.wait, and LockSupport.park started on an adapter-registered event loop, reported, never thrown: advice on every public LockSupport.park* method, which returns after one volatile read off event loops until a loop is registered, and on every JDK the sleep and wait call sites of the application's classes rewritten to the bridge's substitutes, since those methods are native on JDK 17. A park shorter than 1 ms is only counted. The Blocking tab shows rows by attribution, operation, event loop's thread family, and call site, with calls, interrupted or failed calls, total and longest blocked time, and up to three exemplar request ids. On Spring MVC, without an event loop, the sensor is not-applicable until a WebClient's loop is registered. The WebFlux and Quarkus samples' GET /api/side-effects/event-loop-sleep shows a sleep row on the event loop's family, and their GET /api/side-effects/worker-sleep counterexample, the same sleep on a worker, shows none. A connect, a name lookup, or a DatagramSocket send the network sensor records, and a file the files sensor records, is a network or file row too when it started on an event loop, never Netty's non-blocking connect.
  • With the agent, the three samples' GET /api/side-effects/sdk-call shows a connect row to localhost:<port> from LicenseSdkClient#check that is not-captured, and its request header never appears; the counterexample GET /api/side-effects/rest-call, through the recorded REST client, shows no not-captured row.
  • With the agent, GET /api/side-effects/report on the three samples shows a write of ./target/bootui-side-effects/report-{n}-{n}-{n}.csv (working directory, application origin) attributed to the route, and, with environment opted in, a read of sample.report.title; the report's contents and the property's value never appear. The counterexamples: GET /api/side-effects/scratch's file is under $TMPDIR, and GET /api/side-effects/log's JDK logging handler file is grouped apart as logging.
  • The opt-in thread-activity sensor records Thread.start, VirtualThread.start, the ThreadPoolExecutor, ForkJoinPool, and thread-per-task executors' creations and shutdowns, a pool's own workers being its executor's row. The Threads and leaks tab shows rows by attribution, kind (thread, virtual thread, executor), target (the started thread's family or the executor's class), call site, and origin (application, library, jdk, the last two grouped apart), with how many were started per request, how many a request's application code left running when its response completed (checked 250 ms after it, once), and executors shut down or reclaimed without a shutdown. With the agent and thread-activity opted in, the three samples' GET /api/thread-activity/left-running shows a report-refresher-{n} thread left running and GET /api/thread-activity/own-pool an executor left running; the counterexamples GET /api/thread-activity/joined (a thread joined before the response) and GET /api/thread-activity/closed-pool (an executor shut down in finally) are never left running, and neither is a library's or the server's own pool.
  • The opt-in thread-locals sensor hooks nothing: when a request's or a job's scope on a pooled platform thread opens and closes (a Spring MVC request on its worker, a request's task on a pool's own worker, Spring WebFlux work Reactor's context propagation runs on boundedElastic, a Quarkus blocking resource method or managed executor task on its worker, a scheduled run), it scans the thread's thread-local maps, and a thread local with a value at the close that had none, or was absent, at the open is left set; a null value counts as cleared. It reads keys and value nullness only, never a value. The Threads and leaks tab shows rows by attribution, kind (left set, left set (inheritable), left set (with initial value)), target (the static field holding it, resolved on the drain thread without initializing a class, else a hint or the thread local's class), origin, how many times it was left set, and by how many requests; there is no call site ("set during the request"). Event loops, virtual threads, and BootUI's own thread locals are never reported; frameworks' thread locals that clear themselves, and per-thread caches with an initial value outside the application, are dropped and counted. With the agent and thread-locals opted in, the three samples' GET /api/thread-locals/leak shows TenantContext.CURRENT left set; the counterexamples GET /api/thread-locals/cleared (removed in finally), GET /api/thread-locals/nulled (set to null), and, on Spring MVC and Quarkus, GET /api/thread-locals/before (set by a filter before BootUI's scope) never appear, and GET /api/thread-locals/cache's withInitial date format is left set (with initial value). The Quarkus sample's ScheduledTenant, on when side-effects-seed.scheduled-every sets its period, leaves TenantContext.JOB set from a scheduled run: a row of scope execution with no request.
  • The resources sensor (M5-5g, D46), on by default (D47), tracks the streams, channels, and sockets the files and network sensors record opening (sockets and file streams, both by default) for a request or a job with an application frame on the stack, and the JDK's close methods. The Threads and leaks tab shows rows by attribution, resource kind (file input stream, file output stream, random access file, file channel, socket, socket channel), target (the masked path pattern or host and port), call site, and origin (Opened by the application, or Opened by a library the application called), with how many were still open 250 ms after their request's response completed and closed after it (a hand-off, as a pool's connection), and how many the collector reclaimed without close(), the leak. With the agent, the three samples' GET /api/resources/leaked-stream shows a file input stream reclaimed without close(); the counterexamples GET /api/resources/closed-stream (try-with-resources) shows nothing, and GET /api/resources/pooled-client (the JDK HttpClient's pool) never a reclaim.
  • bootui.agent.sensors defaults to executors, inventory, code-paths, processes, network, files, blocking, and resources; threads, environment, thread-activity, thread-locals, caught-exceptions, and security-sinks remain opt-in. All fourteen sensor ids listed here ship in this version, including all nine Side Effects sensors; no placeholder sensor ids remain. An unknown id fails the application's start, on Spring and Quarkus alike, while the BootUI agent is attached, with a message naming the accepted ids.

5.8 Startup Timeline

Purpose: answer "What made startup slow?"

Data sources:

  • Actuator startup endpoint when configured.
  • Spring ApplicationStartup; BootUI installs a BufferingApplicationStartup automatically while active unless bootui.startup.enabled=false or bootui.startup.capacity<=0.

Features:

  • Show startup steps sorted by duration.
  • Show timeline view.
  • Filter by tag.
  • Highlight slowest steps.
  • Explain when startup data is unavailable and how to re-enable or provide startup buffering.

Acceptance criteria:

  • Missing startup data does not break the UI.
  • The UI gives a clear unavailable state when startup data is absent.

5.9 Metrics Panel

Purpose: answer "Which Micrometer meters exist, how are they tagged, and what are their live values?"

Data sources:

  • Micrometer MeterRegistry.
  • Spring Boot Actuator metrics auto-configuration when present.
  • A curated, versioned BootUI catalogue of well-known meter families, used only when the registry itself documents nothing.

Features:

  • List meters by name, description, base unit, and Micrometer type.
  • Group meters by provenance: the integration family that registered them, with the contributing library named.
  • Explain a meter from its registry description first, then from the curated catalogue, and say which source was used (NATIVE, CURATED, or UNKNOWN).
  • Search meters by name or description.
  • Filter meters by type, provenance group, classified/unclassified provenance, and explanation source.
  • Inspect a meter's current measurements.
  • Show available tag keys and values for each meter.
  • Filter live values by tag key/value.
  • Render a lightweight live graph for the selected statistic.
  • Poll locally with bounded browser-side history; no external monitoring service is required.

Acceptance criteria:

  • Missing Micrometer infrastructure produces a clear empty state.
  • Browser responses use BootUI DTOs, not raw registry internals.
  • Provenance is evidence-backed: classification uses meter names only, never tag values, and an unrecognized meter is reported as unclassified with no invented explanation. Curated text is static BootUI content, so no application value flows into an explanation and the panel keeps the metrics contract's existing exposure posture: meter names, tag keys, and tag values are reported as the registry holds them and are not passed through the property-masking policy, which applies to configuration values rather than telemetry.
  • Every matched meter belongs to exactly one provenance group, and group counts reconcile with the matched total even when the returned page is truncated.
  • Groups are facets of the type-, search-, provenance-, and explanation-filtered set computed before the group filter is applied, so selecting a group narrows the meter list without collapsing the group summary the user is choosing from. With a group filter applied, that group's count equals the matched total.
  • Classification is name-based, so an application meter that borrows a binder-owned namespace (tomcat.orders.active) is attributed to that binder. Prefixes an application is plausibly likely to reuse (http.server, http.client, kafka, executor, cache) are narrowed to the sub-namespaces the instrumentation actually publishes.
  • The report names the catalogue version that produced its curated explanations.
  • Tag values remain browser-bounded so high-cardinality meters do not freeze the UI.
  • Polling does not overlap slow requests.
  • Switching meter, tag filters, or statistic resets the live graph history.

5.10 Live Memory and JVM Tuning Panels

Purpose: answer "How much heap/non-heap memory is this app using, and what JVM/container options would be reasonable locally?"

Data sources:

  • Java management beans (MemoryMXBean, MemoryPoolMXBean, ClassLoadingMXBean, ThreadMXBean, runtime input arguments).

Features:

  • The Live Memory panel shows live heap and non-heap usage summaries.
  • The Live Memory panel shows memory pool usage.
  • Live Memory, JVM Tuning, Heap Dump, and the Memory advisor share a Low-footprint mode header toggle with an expandable explanation. After a danger confirmation it calls POST /bootui/api/live-memory/footprint with {"enabled":true}, which pauses existing BootUI recorders and empties every BootUI-owned capture buffer that already exists (runtime journal and its aggregates, in-memory Live Activity, HTTP exchanges, traces, SQL statements, REST client calls, transactions, Kafka/RabbitMQ/JMS/WebSocket activity, cache events, scheduled runs, fault-tolerance events, exceptions with their triage status, security events, and captured emails) and then requests a garbage collection, so memory figures reflect the application rather than BootUI. Individual recording settings, configuration overrides, dismissed rules, cached scan reports, run history, and a durable Live Activity store are kept. Stores register through the engine's MemoryOffloadable port; adapters hand over only singletons already created, so the action never instantiates a lazy bean or loads an optional integration. The status (LowFootprintStatus) reports suspension outcomes and warnings; its offload report gives heap used before and after (MemoryMXBean), a non-negative reclaimed estimate, the cleared entry count, each store's outcome (a failing store is reported, not fatal), and whether -XX:+DisableExplicitGC makes the collection request a no-op; System.gc() is only a hint. The action belongs to the Live Memory panel, which is therefore action-capable: global bootui.read-only=true or bootui.panels.live-memory.read-only=true refuses it with the canonical panel denial, and the button is disabled with that reason. The UI makes no request until the user confirms. Panels whose captured data is paused show a "Paused by low-footprint mode" banner with a Turn off low-footprint mode button that posts {"enabled":false} under the same read-only policy; resume warnings and failures are announced, never hidden.
  • GET /bootui/api/live-memory/footprint reads the mode; posting {"enabled":false} releases its hold without restoring cleared data. The one-shot POST /bootui/api/live-memory/offload clears and requests GC without holding recording. MCP exposes get_low_footprint, enable_low_footprint, and disable_low_footprint, all without arguments and under Live Memory policy; CLI projects them as low-footprint status|enable|disable. Both switches need approval and are idempotent. The mode is process-local, off after restart, and has no expiry. The Java agent keeps running. Affected MCP/CLI payloads add lowFootprint beside existing fields, naming actual suspended components, timestamp, warnings, and a resume hint; browser REST DTOs stay unchanged. Mixed native/agent evidence is not wholly paused. Recorder resumes and new method probes are refused while held; memory diagnostics and permitted scans stay usable. See the agent contract.
  • The JVM Tuning panel shows JVM input arguments. Their values pass through SecretMasker and the bootui.expose-values / bootui.mask-secrets policy before serialization, keeping each key visible: -XX:OnError/-XX:OnOutOfMemoryError commands are always masked, and METADATA_ONLY masks every -D value and agent option string while keeping -X/-XX flags and their plain values visible, masking free-text values.
  • The JVM Tuning panel explains spring.threads.virtual.enabled=true, detects whether Spring virtual threads are enabled in the current application, and shows an information or warning bubble. The detected state is explanatory: generated JVM or Kubernetes snippets do not set the Spring property, and virtual threads do not trigger a speculative native-stack discount.
  • The JVM Tuning panel provides a bare-metal JVM memory calculator that partitions a user-chosen target JVM process memory into JVM regions (heap = total − headRoom − directMemory − metaspace − codeCache − stack×threads), using the live loaded-class count from ClassLoadingMXBean (with an explicit 1.25× safety factor), the greater of the 10 MiB direct-memory fallback and observed direct-buffer use, and a platform-thread count floored at 250 by default.
  • The JVM Tuning panel suggests bare-metal JVM options derived from the calculator output, including -Xms/-Xmx (equal for a fixed modeled heap), -XX:MaxMetaspaceSize, -XX:ReservedCodeCacheSize, and -Xss. It deliberately leaves the running JVM's collector unchanged and does not synthesize direct-memory, pre-touch, compact-header, string-deduplication, or out-of-memory policy flags from a memory snapshot.
  • The JVM Tuning panel suggests Kubernetes resources and JAVA_TOOL_OPTIONS with percentage-based heap sizing (-XX:MaxRAMPercentage, -XX:MinRAMPercentage, and -XX:InitialRAMPercentage) instead of fixed -Xmx / -Xms. Equal memory request and limit values are reported as Depends on CPU, because Kubernetes Guaranteed QoS also requires equal, non-zero CPU request and limit values for every container.
  • The JVM Tuning panel lets the user attempt a lower, Burstable Kubernetes request based first on the current cgroup memory snapshot, with committed JVM pools plus observed direct buffers as a lower-confidence fallback. It can include startup/readiness/liveness probes using framework-default paths and the named container port http; operators must verify both when application, management-server, or container-port configuration differs.
  • The model, generated-option inventory, platform behavior, evidence, and limitations are documented in JVM-TUNING-CHECKS.md.

Acceptance criteria:

  • Memory values serialize through stable BootUI DTOs.
  • Suggested options are clearly presented as recommendations, not automatic changes.
  • JVM argument disclosure is reviewed as part of release hardening.

5.11 Vulnerabilities Panel

Purpose: answer "Which Maven dependencies can the local provider identify, and do any have known vulnerabilities?"

The Vulnerabilities checks catalogue defines the supported evidence interpretation, official sources/version caveats, full audit disposition, and deferred limitations. All nine severity-scored advisors (Architecture, Memory, REST API, Spring/Quarkus application, Database, Hibernate, Security, Pentesting, and Vulnerabilities) use the same browser-only evidence-based scoring policy. Their additive annotation-free evidence contract contains boolean usable, boolean coverageComplete, and an immutable, bounded, sanitized limitations list. Usability means at least one applicable check completed or a genuine known-severity finding was observed, before filtering or dismissal. Missing-evidence INFO notices and UNKNOWN-only vulnerability data cannot establish usability; genuine INFO/NONE findings can. Existing counters, status vocabulary, severities, and dismissal identities remain unchanged. Security scan status follows applicable coverage: unknowns produce PARTIAL; inapplicable checks alone do not make coverage incomplete or establish usability.

Data sources:

  • The application's embedded CycloneDX SBOM (META-INF/sbom/application.cdx.json or META-INF/sbom/bom.json, the files Spring Boot's /actuator/sbom serves), whose purl values carry the groupId. Components declared not shipped (scope: "excluded" or cdx:maven:package:test=true), with their nested components, are skipped unless the archive census finds their JAR.
  • Maven metadata (META-INF/maven/*/*/pom.properties) discovered from the running application's classpath.
  • The java.class.path entries, read through the Maven repository directory layout or an adjacent .pom.
  • Quarkus instead reads coordinates captured from its build-time resolved application model. A missing/blank model, a malformed entry, or a coordinate the build step could not encode reports UNAVAILABLE coverage, never COMPLETE.
  • OSV.dev Maven vulnerability data for explicit on-demand scans.

Features:

  • List locally discovered Maven dependencies by group, artifact, and version, without claiming a verified runtime graph.
  • Keep the initial inventory local-only; no external vulnerability lookup runs on page load.
  • Provide an explicit "Scan with OSV.dev" action that sends Maven package names and versions to OSV.dev.
  • Show scan status, vulnerable dependency count, advisory count, severity breakdown, advisory links, aliases, and fixed versions when available.
  • Calculate the same numeric score in the panel and the Scorecard for SCANNED or PARTIAL reports with valid severity and evidence data and evidence.usable: true; all-skipped, all-failed, and vacuous passes cannot establish 100. ERROR/DISABLED/NOT_SCANNED and malformed reports never score. Missing metadata is unknown, not complete. Legacy reports without valid explicit evidence remain unscored; findings remain visible. Genuine INFO observations in modern reports remain eligible.
  • Dependency evidence is usable with known-severity findings (including NONE), or a fully assessed package whose retained advisory severities are all known (or whose query result is genuinely empty) with both assessment.queryComplete and assessment.detailAssessmentComplete true. The former means that dependency exhausted its query pages; the latter means all returned details were interpreted or conclusively withdrawn. Failed, missing, capped, mismatched, or unresolved details cannot establish a no-match. Preserve these flags through cached reports, dismissals, and EPSS enrichment. UNKNOWN remains visible and excluded from penalties; UNKNOWN cannot establish usability and always limits coverage, even after dismissal; UNKNOWN-only evidence remains unscored unless independent usable evidence exists. Dismissed known findings remain observed evidence. An empty active summary is not a genuinely empty retained advisory list.
  • Retain findings and diagnostics independently of eligibility. Usable numeric scores lead with findings and a neutral Results available status. Incomplete inventory, query/detail gaps, unknown severity, or incomplete applicable checks remain in an initially collapsed, keyboard-accessible Scan notes disclosure in the dedicated panel, even at 100, with Scan notes available in score accessible names. The Scorecard counts advisors with notes, not individual checks, and links to each panel. Unscored reasons and whole-scan failures remain prominent. Complete assessments need no additional scope paragraph. Raw statuses, evidence, and eligibility are unchanged. Intentionally inapplicable checks alone do not make coverage incomplete. Penalties stay CRITICAL/HIGH/MEDIUM/LOW/INFO/NONE = 25/10/3/1/0/0 with unchanged clamp, and rounding. Numbers are Known-findings scores, not app-health grades; advisor panels keep neutral numbers, including for partial 100. The Scorecard restores a prominent Overall score: the rounded arithmetic mean of eligible available visible advisor scores and eligible GitHub, with Average of N scores. No eligible scores means Not scored, with a prompt to run an available scanner. Invalid, missing, unscanned, and confirmed-empty reports are excluded, not replaced with zero or 100. Usable partial scores retain their existing penalties. Retained finding severities and advisor assessment counts remain visible. The overall summary uses the original color-coded circular gauge, with the numeric score centred in the ring, a horizontal desktop layout that stacks on narrow screens, and a Points deducted per score grid of eligible contributors. Each deduction is that contributor's score minus 100, not an additive overall-score calculation. Overall gauge bands are Good (80–100), Needs attention (50–79), and At risk (0–49), qualified as descriptions of scored results rather than application safety or completeness. Individual Scorecard scores, including GitHub, use the same green/amber/red thresholds with theme-aware text colors, visible numbers, and retained severity labels. Keep assessed, failed, retained-severity, and scan-notes counts visible in the compact summary. The Scorecard uses Scan complete for scored report badges and Connected for GitHub's connected badge; scan completion does not assert complete coverage. Light-theme score numbers use saturated semantic colors. Confirmed empty scope (usable: false, coverageComplete: true, no limitations) counts as assessed, not incomplete; unscanned reports and request failures remain distinct. The average is not an application-health or completeness verdict. GitHub is outside advisor counts and severity totals. Its Security-alert score is 100 minus 10 per reported alert, clamped to 0–100, not a severity assignment. It requires an available, connected, authenticated report with exactly one AVAILABLE signal and a nonnegative safe integer count for each of Dependabot alerts, Code scanning alerts, and Secret scanning alerts. Missing, empty, duplicate, malformed, or unavailable signals leave GitHub unscored and excluded from the average; known available zeros score 100. Connection state, actual counts, unavailable states, and failures remain visible. Refresh is user-triggered only. Retain the last accepted GitHub report and score while refreshing or on request failure; an authoritative unavailable report clears its score. GitHub's numeric score uses the shared green/amber/red thresholds, but has no separate health-grade badge. Unavailable counts are not zero. Coverage status and reasons remain visible with numeric results in each advisor panel. Do not infer percentages, comparable cross-advisor rule counts, or confidence weights. Dismissals change penalties, not application safety or coverage.
  • Report refresh after dismissal is GET-only. Preserve the last accepted report on busy or transport failure, but replace its assessment when a new authoritative report arrives. Cached hydration, request ordering, pending refresh, and NOT_SCANNED restart clearing remain intact. No backend numeric scoring or new MCP/CLI command is added.
  • Interpret affected entries in the JSON-free shared engine with exact Maven ecosystem and package matching, allowing only the literal * package wildcard, not arbitrary globs or other Maven repository ecosystems. Explicit versions and supported Maven ECOSYSTEM ranges form a union across matching entries. Applicability is matched, not matched, or unresolved; unsupported SEMVER/GIT, malformed, or contradictory detail evidence never silently removes a query-derived finding. Retain it with PARTIAL and an explanation, omitting unsupported package-specific claims.
  • Evaluate unsorted/reintroduced events with Maven ordering: introduced inclusive (zero precedes every version), fixed exclusive, last_affected inclusive, limit exclusive (wildcard unbounded); limits are scope bounds, not fixes, and multiple limits expand scope. Require one supported event type per event, an introduction per range, and no coexistence of fixed and last_affected in an event array.
  • Derive severity only from typed CVSS_V4 vectors with valid v4.0 syntax and typed CVSS_V3 vectors with valid v3.0/v3.1 syntax. Validate the full vector, including optional metrics, rejecting empty/trailing segments, unknown metrics, duplicates, malformed segments, and invalid values. Accept valid metric orders. For v3, require all eight Base metrics and calculate Base only with FIRST's scope equations and Roundup. For v4, require all eleven Base metrics and score the vector as published with FIRST's reference MacroVector algorithm, so a supplied Threat or Environmental metric applies and Supplemental metrics never do. Within the selected level, prefer the highest valid v4 assessment across all applicable matching package entries and use the highest valid v3 assessment only when no v4 assessment is valid; never compare v3 and v4 scores. If none supplies package severity, apply the same preference to the global assessments. Invalid/unsupported applicable package severity must not borrow a conflicting top-level score from a schema-invalid dual-level record. Retain the recognized top-level database_specific.severity label fallback, otherwise UNKNOWN; this is provider compatibility, not a universal OSV scale. Normalize MODERATE to MEDIUM and valid zero to NONE. Never reinterpret bare numbers or CVSS v2 as v3/v4, and never drop unsupported findings. An advisory whose own OSV ID carries the OpenSSF Malicious Packages MAL- prefix is CRITICAL with no score, and its details lead with removal guidance.
  • Exclude advisories marked withdrawn by OSV from results and counts.
  • Follow OSV /v1/querybatch pagination (next_page_token) until every query is exhausted or a bounded page-count safety limit of 20 page rounds per chunk is hit, and partition the outgoing package list into batches of at most 1,000 queries (the OSV server implementation's hard limit), merging every page/batch back into one result set. Validate that every response contains exactly one structurally valid result per submitted query; never reinterpret a missing/short/malformed response as an empty result. Require every returned vulnerability reference to be an object with a non-blank id. Preserve validated earlier pages, including completed queries in the same chunk, as PARTIAL if a later page or chunk fails. packagesScanned counts only queries exhausted without a continuation token, including at the cap; an empty token-bearing page is not complete. Explain unfinished/failed queries in the message, not packagesSkipped.
  • Fetch distinct advisory details (GET /v1/vulns/{id}) with a small bounded concurrency (up to 10 at a time) instead of one at a time, so scans against a dependency tree with many distinct advisories stay responsive. De-duplicate repeated ids per dependency and require each detail response id to match the requested id; mismatch/missing-id responses count as failed fetches. Keep the detail-stage withdrawn check because POST queries omit withdrawn records while GET /v1/vulns/{id} can return them.
  • Enrich each advisory linked to a canonical CVE through either its own id or an alias with FIRST.org EPSS exploit-probability data (probability + percentile) in one or more batched requests per scan, each respecting FIRST's 2,000-character maximum for the comma-separated cve parameter, alongside the OSV calls; EPSS is a likelihood-of-exploitation signal that complements, rather than replaces, the CVSS severity-if-exploited score. EPSS responses may enrich only CVEs requested in that chunk and must contain finite probability/percentile values from 0 to 1. Validate an object root, array data, and supplied numeric total/offset/limit metadata. Follow smaller service pages with progress validation, bounded to min(20, requested CVE count) pages per chunk. Retain successful earlier pages/chunks on failure, appending requested/available/no-data counts or a failure explanation to scan.message without changing OSV status. A successful exhausted no-row result is no data, not zero; malformed/empty envelopes are failures. Select maximum AVAILABLE probability across own/retained CVE aliases, with the same record's percentile and a stable CVE tie-break. Valid zero is available, not unknown. This is a prioritization heuristic, not combined exploit likelihood. Lookups can be disabled independently, and disabled/no-CVE enrichment makes no FIRST call.
  • Keep fixedVersions and fixAvailable with evidence-backed candidate semantics: only a fixed event closing a supported affected interval containing the installed version can supply a target. Require a positively newer Maven comparison and recheck against all matching entries, including explicit versions and overlapping/reintroduced intervals; affected or unresolved targets are not verified upgrades. Filter before de-duplication, Maven ordering, and the ten-candidate limit. Neither last_affected nor limit, Git hashes, or unsupported range domains supplies a target. False/empty means no candidate was established, not that the dependency is unaffected or no upstream fix exists. Candidates do not guarantee compatibility, artifact publication, or reachability remediation.
  • Report provider coverage separately from scan completion. Spring enumerates conventional/manifest-selected nested libraries or JARs from java.class.path and local application-classloader URLs, and attributes names to coordinates. This includes MVC and WebFlux applications extracted with jarmode=tools extract --layers --launcher, with layers merged and launched through JarLauncher; the dependency JARs need not appear in java.class.path. Archive counts remain separate from SBOM component totals and completed OSV queries. An SBOM alone never changes an unobservable archive census to COMPLETE. Report unidentified names as INCOMPLETE with exact reported counts and at most 200 names plus truncation, and unavailable census as UNAVAILABLE. Before reporting an archive unidentified, read only its manifest and entry names (for a stored nested archive, only its central directory and a bounded manifest): an archive whose every class (at least one) lives in the application's multi-segment base packages, with no META-INF/maven/ descriptor and, when a Spring Boot layers.idx defines an application layer, placed in that layer (which also admits each base package's multi-segment parent), is the application's own module and is reported as first-party (archivesFirstParty, at most 200 firstPartyArchives plus truncation), not as a gap; spring-boot-jarmode-tools is identified from a matching manifest title and version. archivesFound always equals identified plus unidentified plus first-party. COMPLETE describes the provider's reported identification, not independently verified runtime completeness: filename attribution overclaims remain deferred below.
  • Report packages dropped by the bootui.vulnerabilities.max-packages bound as scan.packagesSkipped rather than letting packagesScanned present a truncated scan as a complete one.
  • Support disabling OSV scans with bootui.vulnerabilities.osv-enabled=false.
  • Allow dismissing/restoring an individual vulnerability finding for a specific dependency, excluding it from the vulnerable count and severity rollups until restored, consistent with the dismiss/restore workflow shared by every other advisor. Recompute dependency ordering from active severity after every dismissal change, and disable the Vulnerabilities panel's dismissal controls under panel read-only policy.
  • Preserve DTO fields and advisoryId::packageName dismissal keys, independent of installed version. Count advisory occurrences per dependency, not unique CVEs; alias-cluster merging is deferred. REST, MCP, and CLI use the same interpretation and unchanged names/arguments. Shared local-only/Host/cross-site-write/read-only policy is unchanged.
  • Normalize each advisory to the classes or methods it names (advisorySymbols, Java binary names, at most 20) with their provenance (advisorySymbolSource): OSV from the matching affected[] entries' ecosystem_specific or database_specific imports[].symbols, affected_functions, or symbols; else ADVISORY_TEXT from fully qualified class names in the summary and details; else an explicit NONE.
  • Runtime reach: with the BootUI agent's inventory sensor recording this run, both GET {api}/vulnerabilities and POST {api}/vulnerabilities/scan, and so get_vulnerabilities_report and vulnerabilities_scan, carry runtimeReach on the report (available, unavailableReason, generation, note, and incompleteReason, said once when no dependency can be shown as not loaded), on each dependency, and on each advisory (status, reason, classesLoaded in this run, classesLoadedTotal in this JVM, loadedThisRun, firstRoute, loadedClasses). Reach is read when answered, from the agent's in-memory class-load evidence and never with the cached scan, and only while the Code Inventory panel, whose evidence it is, is enabled; otherwise available is false with the reason and rows carry null. Statuses: AFFECTED_CLASS_LOADED (a class the advisory names loaded from the dependency's own jar), LOADED, NOT_LOADED (no class of the jar loaded in this JVM, said only when the evidence could have shown a load), and UNKNOWN with its reason. firstRoute is HTTP Exchanges evidence, omitted while that panel is not visible. The UI adds a Runtime reach column and filter only when available is true.

Acceptance criteria:

  • Dependency and advisory data serialize through stable BootUI DTOs.
  • External scanning is user-initiated and clearly labeled in the UI.
  • A failure fetching the initial OSV batch query returns a clear error status while preserving the local dependency inventory; a failure fetching one advisory's details does not discard advisories that were already fetched successfully, degrading the scan to a partial-success status instead of an outright error.
  • Scan size is bounded by configuration so large classpaths remain responsive.
  • Existing per-request byte limits remain 5 MiB for querybatch and 1 MiB for details/EPSS. Per-request timeouts, no automatic redirect following, configured service bases, and interrupt restoration remain unchanged; a total scan deadline and retry/backoff policy are deferred.
  • Initial/error/partial UI states must not label an empty advisory list as a clean "None found" result, and a report with unidentified archives or skipped packages must never render as full coverage.
  • An unreadable Spring pom.properties resource, a malformed or unreadable SBOM, and an unreadable classpath archive are each logged and skipped without discarding entries that did resolve; Quarkus continues to use its build-time resolved runtime dependency model and fails soft on malformed entries.
  • The panel's and the Scorecard's scores, severities, counts, evidence, and penalties are identical with the BootUI agent on and off: runtime reach only adds runtimeReach fields.
  • An unknown runtime reach never reads as NOT_LOADED: without the agent's class-name evidence, with its class-load recorder not running or not done walking the loaded classes, with code sources past its limit, when the dependency's jar was not found or holds no classes, when a class of one of its jar's packages was defined without a code-source location, or with a loaded jar that may be the dependency (no Maven metadata and the artifact in its file name, or shaded coordinates naming it), reach is UNKNOWN, as it is for web-resource jars and artifacts whose classes the agent never records.

Known limitations: inventory-discovery repairs are explicitly deferred. Spring filename de-duplication and case-insensitive attribution without group identity can overstate coverage for same-basename archives or ambiguous classifiers. PURL literal-plus decoding/namespace rewriting, SBOM runtime scope beyond test-only and excluded components, and unreadable container/archive diagnostics need separate fixes. The SBOM traversal limit counts resolved distinct coordinates, not inspected nodes, and the whole JSON is parsed first. Quarkus COMPLETE reflects its wholly decoded build-time model. Neither a provider nor a consumer trusting that flag independently verifies a complete runtime inventory.

The dependency inventory is coordinate-based (one resolved JAR = one artifact coordinate), and no JAR manifest header carries a groupId (the curated spring-boot-jarmode-tools exception above relies on its fixed, known group) — Implementation-Title is a display name as often as an artifact id, and Implementation-Vendor-Id is not a group id. An application built without a CycloneDX SBOM therefore cannot resolve coordinates for the many artifacts published with no Maven descriptor (Spring Framework, Spring Boot, Spring Security, tomcat-embed-*, hibernate-core, kotlin-stdlib, the PostgreSQL driver, and the opentelemetry-* and micrometer-* families among them), and inside a repackaged fat JAR the java.class.path fallback is dead too. Archives detected as unidentified by the census are reported through coverage, subject to the attribution limitations above; the panel suggests an SBOM to improve identification, not to certify completeness. External hash-based coordinate lookup would add another service and configuration surface, so it is deliberately not done. Separately, a vulnerable library relocated/repackaged inside a shaded or uber JAR has no coordinate of its own, so it is invisible to the inventory and cannot be flagged — the same reduced-fidelity honesty precedent already documented for other panels (for example Cache, Beans). Direct-vs-transitive dependency provenance ("introduced through") is not yet tracked on either adapter; Quarkus could source it from its build-time application dependency graph, but Spring's classpath-based inventory has no equivalent graph today, so this is deferred rather than shipped asymmetrically.

CVSS v2 calculation, full assessment/vector provenance (including which CVSS version produced score), selected EPSS CVE/date/model fields, cross-request date pinning, and a total scan deadline remain deferred. Enrichment uses the own CVE ID and at most 20 retained aliases, not every upstream alias. The browser's same-package lexical version sorting remains a separate presentation limitation. Completed OSV queries do not prove exploitability, reachability, absence of unknown advisories, or application safety.

5.12 Scheduled Tasks Inspector

Purpose: answer "Which scheduled tasks are registered?"

Data sources:

  • Spring ScheduledTaskHolder.

Features:

  • List registered scheduled tasks.
  • Show runnable description, trigger type, interval/cron expression, initial delay, and display units.
  • Show an empty state when scheduling infrastructure is absent or no tasks are registered.

Acceptance criteria:

  • Opening the panel never invokes scheduled tasks.
  • Spring wrapper runnables are displayed with the most useful available task description.

5.13 HTTP Probe Panel

Purpose: issue safe local HTTP requests to the running app from the developer console.

Data sources:

  • BootUI internal /bootui/api/http-probe endpoint using a local HttpClient.

Features:

  • Send requests to paths relative to the application root.
  • Normalize method and path.
  • Restrict targets to localhost.
  • Support request bodies only for methods that can carry a body.
  • Bound every inbound field before any request is sent: method (32 bytes), path (2 KiB), request body (64 KiB), header count (50), header name (256 bytes), header value (8 KiB) and total header size (32 KiB), all measured in UTF-8 bytes.
  • Require explicit confirmation before sending any method other than GET or HEAD.
  • Display status, selected response headers, body, timing, and errors.

Acceptance criteria:

  • BootUI never proxies arbitrary external URLs.
  • GET and HEAD probes run directly; unsafe probes do not reach the backend until the developer confirms them.
  • Unsafe-body behavior is explicit and predictable.
  • Response headers are filtered to a small allow-list.
  • Response bodies are truncated at the probe byte budget and flagged as truncated.
  • Input that exceeds a probe limit is rejected with the canonical 400 and {"error": ...} body on every adapter, before any request reaches the application; a probe that runs but fails stays a 200 outcome with an error payload.

5.13.1 Pentesting Panel

Purpose: run local OWASP-oriented hygiene checks without turning BootUI into an invasive scanner.

Data sources:

  • Passive Spring application-context metadata. Quarkus CORS, OIDC, and TLS configuration belongs to the Security panel (QS-*), so Quarkus scans rely on the synthetic responses.
  • Spring MVC request-mapping and servlet-security metadata when available. WebFlux and Quarkus report this inventory as unavailable rather than empty/clean.
  • At most one GET and one OPTIONS request to literal 127.0.0.1, targeting a deliberately unlikely host-application path under the validated application context path, never BootUI's /bootui or /bootui/api paths. The client has a two-second timeout, follows no redirects, and uses no proxy.

Features:

  • Show a not-scanned report until the user runs the scan.
  • Run bounded local checks for common security headers, CORS behavior, cookie flags, verbose error exposure, Spring Security wiring, and actuator exposure against the host application rather than BootUI itself.
  • Cross-reference findings with OWASP Top 10 categories such as A01, A02, A04, A07, and A10.
  • Show Findings by severity, consistent with other advisors, instead of a separate OWASP Top 10 coverage panel. Category metadata is informational review guidance, never a pass. Report unavailable or truncated dependent evidence honestly, with detailed limitations in the panel's scan notes.
  • Apply persisted dismissedRules by exact PT-* check ID on scan responses and cached reads. Retain findings with an explicit dismissed boolean, but exclude dismissed findings from findingsFound, scan.findingsFound, severityCounts, and panel/Scorecard penalties. Offer Dismiss and a collapsed Dismissed rules list with Restore, disabled under panel read-only policy. Refresh after dismissal with a cached read, never another scan.
  • Preserve scan time/status, check counts, coverage, and evidence when applying dismissals. Equivalent Security and Pentesting findings remain independently dismissible; accepting a finding does not imply a completed or passing check.
  • Hand off dependency vulnerability coverage to the Vulnerabilities panel.
  • Clearly mark injection payloads and endpoint access-control probing as skipped.

Acceptance criteria:

  • The scan action is explicit and local-only.
  • BootUI UI/API paths are excluded from passive mapping inventory and active synthetic requests.
  • The target is fail-closed: only ports 1..65535, literal 127.0.0.1, and an unambiguous context path up to 256 characters are accepted; rejected targets cause no request.
  • BootUI does not sweep discovered application endpoints, send exploit payloads, follow redirects, consult configured proxies, resolve a probe hostname, or contact external hosts.
  • Probe bodies, headers, endpoint mappings, and enumerable framework metadata have deterministic retention/inspection limits. Exceeding one produces a PARTIAL scan and INDETERMINATE dependent coverage instead of success-shaped silence.
  • Individual checks have stable identifiers so additional checks can be registered without expanding the active HTTP probe surface.
  • Reports serialize through stable BootUI DTOs and do not include raw response bodies, cookie values, credentials, or full OAuth/OIDC issuer details.
  • Findings are presented as heuristic review prompts, not definitive exploit claims.

5.14 Log Tail Panel

Purpose: stream recent local application log lines in the browser.

Data sources:

  • BootUI Logback appender installed when Logback is on the classpath (Spring Boot).
  • A java.util.logging handler attached to the root JBoss LogManager logger (Quarkus).

Features:

  • Show recent buffered log lines.
  • Stream new log events with Server-Sent Events.
  • Pause, resume, clear, and filter by severity in the browser.
  • Apply the value-exposure rule that exception messages follow to every message when it is read: mask secret-like assignments and the credential after an authorization scheme, keeping the scheme, under MASKED, omit the message and set messageOmitted under METADATA_ONLY, and return it verbatim under FULL or with bootui.mask-secrets=false.

Acceptance criteria:

  • On Spring Boot, the panel is classpath-gated and unavailable when Logback is absent.
  • Log events are shaped into stable DTOs before reaching the browser.
  • The snapshot, the SSE stream and its replayed backlog, get_log_tail, and bootui logs tail apply the same rule on every stack, and a runtime exposure change applies to the next snapshot and streamed line without a restart.
  • An omitted message is marked in the panel, never shown as an empty line.

5.14.1 HTTP Exchanges Panel

Purpose: inspect recent inbound HTTP requests handled by the running application.

Data sources:

  • Spring Boot Actuator HttpExchangeRepository, with BootUI's own bounded, failure-preserving BootUiHttpExchangeRepository when no application repository exists.
  • On Quarkus, the framework-neutral HttpExchangeBuffer, fed by a dev/test-only Vert.x route filter.

Features:

  • Show recent exchanges with timestamp, method, path, status, duration, response size when available, and trace id when a common propagation header or the server's active tracing context supplies one.
  • Show request and response headers in row details.
  • Offer a client-side Copy as cURL action in row details that rebuilds a runnable command template from the retained exchange metadata, without capturing a body or replaying the request.
  • Provide server-side filtering by path/URL/trace id, method, status class, and route with bounded paging.
  • Hide BootUI self-requests by default through bootui.monitoring.exclude-self. Where BootUI owns recording they are never recorded, judged from the decoded path below the context path and never the query string, so console polling never displaces application exchanges.
  • State the retained window above the list: exchanges kept of the capacity, the reserved share and how much of it holds failed or slow exchanges, and evictions since startup.
  • Label every exchange with its route and link it to its Live Activity request profile.
  • Rank routes over the retained window (GET /bootui/api/http-exchanges/routes): per method and route, the request count; 2xx, 3xx, 4xx, and 5xx counts; average, p50, p95, p99, and maximum duration; and share of retained request time, ranked by requests, total time, p95, maximum, or errors. Each route links to the exchange list filtered to it.
  • Resolve a route from the framework's handler template, then the single best declared mapping, then a masked path, exactly as SQL Trace route attribution does, and report the source as FRAMEWORK_TEMPLATE, DECLARED_MAPPING, or MASKED_PATH.
  • State the evidence window beside the rankings: retained exchanges, buffer size, evictions, the oldest retained exchange, and hidden BootUI exchanges.

Acceptance criteria:

  • The recorder is bounded by bootui.http-exchanges.max-exchanges, defaulting to 200.
  • Retention is failure-preserving wherever BootUI owns recording (Quarkus, and Spring when BootUI contributes both the repository and its recording filter). bootui.http-exchanges.reserved-share-percent (default 25) of the capacity is reserved for the most recent 5xx exchanges and exchanges at or above bootui.activity.request-slow-threshold-ms (default 1000; 0 disables slow classification). Routine exchanges are evicted first; the reserved share evicts its own oldest exchange only when full. The reservation never adds memory and never takes the whole buffer. SQL Trace and REST Client buffers apply the same engine policy with their own slow thresholds and bootui.sql-trace.reserved-share-percent / bootui.rest-client-trace.reserved-share-percent.
  • The report carries a retention object (capacity, reserved capacity, retained, reserved, evicted, slow threshold) computed from the same snapshot as the exchanges. When the application provides its own HttpExchangeRepository or recording filter, BootUI never replaces it, keeps its former plain behavior, and reports applicationManaged: true with only the retained count.
  • Route counts and durations reconcile with the retained, visible exchanges; status classes add up to the request count, and percentiles are exact nearest-rank values over each route's timed exchanges.
  • Route rankings return the union of each criterion's top routes (25 by default, at most 100) with the criteria each route leads, state how many distinct routes were retained, and break ties on the route id in plain character order. ?route=<id> always returns that route's row. Ambiguous declared mappings produce no template but still mask every parameter position they declare, framework and declared templates render identically, and a route never carries a query string.
  • A value the exchange source does not report, such as an application repository's capacity or evictions, is null rather than guessed. BootUI-owned buffers report both, from the same snapshot as the ranked exchanges.
  • Secret-like headers and query parameters are masked unless value exposure is explicitly set to FULL. Sensitive parameter names are matched percent-decoded, so a URL-encoded name cannot evade masking, and the same masking applies to a query-shaped URI fragment.
  • URI authority user-info credentials (user:secret@host) are removed unconditionally, including under FULL: host and port survive, the credential never reaches the browser.
  • The panel is read-only and returns a stable unavailable DTO when no HttpExchangeRepository is available.
  • Copy as cURL performs no request and changes no state; it shows the command before copying, keeps query-parameter names but replaces every value with a placeholder, copies only allowlisted unmasked request headers, POSIX-quotes every argument, copies the recorded path without normalization, and explains what it left out. It reports a clear reason instead of guessing when the recorded URL or method is unusable.

5.14.2 Live Activity Panel

Purpose: provide one diagnostics "home base" that merges BootUI's already-captured signals into a single reverse-chronological activity stream, plus a Symfony-style per-request profiler for drilling into a single request.

Data sources:

  • By default (bootui.activity.feed-source=journal) the feed renders the runtime journal's retained events, nesting every child under its request or execution by id; when the journal is disabled or not recording, the panel buffers serve the feed instead. ?source=buffers on one request selects the merge of the panels' own buffers described below; the property's buffers value was removed in 2.0.0 and fails startup with a message naming journal.
  • Reuses the existing HTTP Exchanges, SQL Trace, REST Client, Exceptions, Security Logs, Email, and Health controllers/DTOs. The panel adds no new instrumentation and reads no raw buffers directly, so masking, bootui.monitoring.exclude-self, and buffer bounds are inherited unchanged from each source panel.
  • Scheduled-task-run capture (both adapters): a bounded, framework-neutral ScheduledTaskRunStore in bootui-engine. On Spring, a Spring-specific ObservationHandler/SchedulingConfigurer pair taps Spring Framework's own ScheduledTaskObservationContext instrumentation (present since Spring Framework 6.1) — no AOP proxying or bean wrapping. On Quarkus, QuarkusScheduledTaskRunRecorder observes the ordinary CDI SuccessfulExecution/ FailedExecution events the scheduler always fires after every execution (the single-instance JobInstrumenter SPI is unusable since quarkus-opentelemetry already claims it); the trigger's getFireTime() stands in for a start timestamp since these events only fire on completion. Only @Scheduled method tasks are observed this way, so a manually registered Runnable/Trigger task does not produce a SCHEDULED entry, consistent with how the static Scheduled Tasks panel lists it with only a generic runnable name. Capped by bootui.activity.max-scheduled-task-runs (default 200, shared config key on both adapters), and self-filtered the same way the static task list is; on Quarkus the observer is additionally gated on the SCHEDULER capability (see Design constraints below).
  • On the Spring servlet and WebFlux adapters, a CacheActivityRecorder (framework-neutral, bootui-engine) is fed by CacheActivityCacheManagerBeanPostProcessor, which decorates every CacheManager bean so both annotation-driven (@Cacheable/@CachePut/@CacheEvict) and direct programmatic cache access are captured transparently, pass-through by default and fail-open. Both beans are wired once in the shared BootUiEngineConfiguration so servlet and WebFlux behave identically. Gated by bootui.cache.activity-capture-enabled (default true) and the Cache panel's own bootui.panels.cache.enabled; bounded by bootui.cache.activity-max-events (default 500). Cache keys are never captured raw — only a short SHA-256 hash (CacheActivityRecorder.hashKey) — so no application data leaves the process even under full value exposure. Both Spring adapters stamp BootUI's request id with or without tracing; optional unambiguous trace-id evidence provides another exact tier. WebFlux never infers ownership from its shared event-loop thread. Quarkus does not capture cache accesses (quarkus-cache's built-in interceptors cast the resolved cache to an internal, non-public AbstractCache type, leaving no comparable runtime interception seam for a Spring-style decorator), so the CACHE event type and the cacheHitRatioPercent KPI are Spring-only for now; see docs/QUARKUS-SUPPORT.md.

Features:

  • The panel-buffer merge is a stream of REQUEST, SQL, REST_CLIENT, EXCEPTION, SECURITY, SCHEDULED, MESSAGING, MAIL, FAULT_TOLERANCE, and (Spring servlet/WebFlux only) CACHE entries normalized to a common shape (timestamp, type, severity, one-line summary, optional duration and correlation id), sorted newest-first and capped by bootui.activity.max-entries. The journal feed adds the journal's own entry types, such as TRANSACTION, LOG, AI, ORM, WEBSOCKET, APP_EVENT, MARKER, and, with the BootUI agent, ASYNC. The since cursor allows incremental polling. Each entry also carries an optional parentId referencing the REQUEST entry it was precisely correlated to (by trace id, serving thread, or request method/path), so the client can nest correlated SQL, REST, exceptions, security events, cache accesses, and captured email chronologically under the request that produced them; the server list stays flat (KPIs, filters, and the sparkline are unaffected). Scheduled runs and consumed messages are top-level executions of their own. Producer messages carrying an exact owner id nest under that request or execution; SQL, REST calls and exceptions carrying an execution id can also nest under a scheduled run or consumed message. Without an exact id, the buffer merge can use an unambiguous trace id; an exception without an owning request additionally has the conservative scheduled-run serving-thread/time-window fallback. Unattributed entries keep a null parentId. The journal-first feed uses its stamped owners instead of reconstructing these buffer relationships. A CACHE entry's summary is "<HIT|MISS|PUT|EVICT|CLEAR> <cacheName>", its detail is "key <hash>" when a key was involved (omitted for whole-cache CLEAR), and a MISS is flagged WARN severity (all other operations OK). A REQUEST entry that was correlated to a Spring Security audit event also carries a securedPrincipal (the caller's principal; null when the request had no correlated security event naming a principal), so the client can flag it as authenticated with a lock icon and a principal tag without opening the profiler. It also carries a sqlNPlusOneSuspected boolean, computed with the identical threshold/logic the per-request profiler uses below, so a request whose correlated SQL looks like an N+1 access pattern can be badged directly in the list without opening its profiler. The client also tints REQUEST rows on a graduated yellow-to-red latency heat scale (crossing 100, 200, 500, and 1000 ms) so slower requests stand out by how slow they are. A SCHEDULED entry is severity ERROR on a thrown exception (with the exception class name and message surfaced as detail), SLOW when its duration meets the same slow-request threshold, otherwise OK, and clicking it deep-links into the Scheduled Tasks panel prefilled with its runnable name.

  • Kafka producer/consumer capture: framework-specific capture hooks feed a shared, framework-neutral KafkaActivityRecorder (bounded ring buffer in bootui-engine), and Live Activity renders those records as MESSAGING entries. On Spring, KafkaProducerCaptureBeanPostProcessor and KafkaConsumerCaptureBeanPostProcessor wrap application-owned KafkaTemplate and @KafkaListener container factory beans — composing with, never replacing, any ProducerListener/RecordInterceptor the application already configured, mirroring the HTTP Exchanges repository-wrapper precedent. On Quarkus, SmallRye Reactive Messaging Kafka interceptors capture the same metadata their callback exposes. Each entry carries topic, partition, offset (consume only), a hash of the key, direction, success/failure, and — for consumed records — listener id and processing duration. Spring also captures the consumer group id; Quarkus leaves group id and producer duration unavailable. Quarkus incoming deliveries carry connector metadata automatically, while an outgoing message is recorded only when OutgoingKafkaRecordMetadata is attached before the interceptor; payload-only emissions that rely solely on channel configuration are not captured. Only metadata is captured; the message value/payload is never captured or masked, since it is an arbitrary application payload with no generic masking strategy. Raw exception messages are never retained; failed operations use generic failure text. A produced message can nest under its exact request/execution owner; a consumed message is a top-level execution anchoring the work its listener records. The listener-id field is intentionally honest about framework limits: on Spring it currently carries the listener container factory bean name (the resolved per-@KafkaListener id is not exposed at the factory-wide interception point), while on Quarkus it carries the channel name. Controlled by bootui.kafka.enabled, bootui.kafka.capture-key, bootui.kafka.max-entries, and bootui.kafka.max-key-length.

  • RabbitMQ producer/consumer capture: on Spring, post-processors compose with every application-owned RabbitTemplate before-publish processor and listener-container-factory advice chain; on Quarkus, SmallRye Reactive Messaging OutgoingInterceptor/IncomingInterceptor implementations are class-presence-gated on quarkus-messaging-rabbitmq. Both feed the shared, framework-neutral RabbitActivityRecorder. Captured metadata is bounded and payload-free: direction, routing metadata, outcome, consumer duration, and—when explicitly enabled—a truncated SHA-256 correlation-ID hash. Quarkus leaves producer exchange, consumer queue, and producer duration unavailable because its callbacks do not expose them, and records an outgoing message only when OutgoingRabbitMQMetadata is already attached. Message bodies, arbitrary headers, raw correlation IDs, and exception messages are never retained; failures use generic text. Published messages can nest under their exact owner; consumed messages anchor their own execution, like Kafka. Controlled by bootui.rabbitmq.enabled, bootui.rabbitmq.capture-correlation-id, bootui.rabbitmq.max-entries, and bootui.rabbitmq.max-correlation-id-length.

  • JMS producer/consumer capture: on Spring MVC and WebFlux only, classpath-gated post-processors wrap application-owned JmsTemplate and AbstractJmsListenerContainerFactory beans without replacing converters, callbacks, listeners, or error handlers. The producer proxy records each public send operation once even when convertAndSend delegates internally; listener adapters preserve the original plain-vs-session-aware dispatch interface and propagate the same failure exactly once. Direct JMSContext/MessageProducer/MessageConsumer calls are outside this Spring-integration capture seam, consistent with the Kafka and RabbitMQ capture scope. A dedicated framework-neutral JmsActivityRecorder and bounded buffer keep JMS retention, counters, and configuration independent from the Kafka and RabbitMQ recorders. Destinations are sanitized and bounded before storage; unknown provider destination toString() values, payloads, arbitrary headers/properties, raw message IDs, and exception messages are never retained. Provider-assigned message IDs exposed through a MessageCreator/MessagePostProcessor are stored only as hashes. Controlled by bootui.jms.enabled, bootui.jms.capture-message-id, bootui.jms.max-entries, and bootui.jms.max-message-id-length; Quarkus reports no JMS integration. A dedicated GET /bootui/api/jms panel report exposes the same retained metadata newest-first, and confirmation-gated DELETE /bootui/api/jms clears that shared buffer. The panel supports destination, message-ID, subscription, listener, failure-type, and direction filtering; it is available on Spring when a JmsTemplate bean is present and reports not yet available on Quarkus. It reports unavailable in a Spring GraalVM native image because its JmsTemplate and listener-factory interception requires runtime class proxies.

  • A KPI strip computed from the same buffers: requests/min, error rate, p50/p95 latency, slowest endpoint, active exception count, SQL/min, slowest query, outbound-call error rate/p95 latency deep-linked to the REST Client panel, health status, heap usage, (Spring only, null on Quarkus) cache hit ratio — the percentage of captured cache reads (HIT/MISS) that were hits, deep-linked to the Cache panel — and a scheduled-task failure count linking into the Scheduled Tasks panel.

  • The p50/p95 latency and the slowest request are computed once, in the shared engine, over every retained request with a duration (latencySampleCount), so they are identical on every adapter for the same evidence. The slowest request carries its resolved route (slowestEndpointRoute, slowestEndpointRouteId, slowestEndpointRouteSource), links to that route's row in the HTTP Exchanges route rankings, and a tie goes to the newest request.

  • ?request=<exchange id> opens that request's profile, so each HTTP Exchanges row links to its profile.

  • Client-side filter chips by type and severity, collapsing of adjacent identical entries with an occurrence count, nesting of correlated children under their request (expanded by default; any active filter or free-text search flattens the feed so the query spans every signal), and a pause/resume control over a live feed pushed by Server-Sent Events (GET /bootui/api/activity/stream): the server emits a tiny coalesced tick whenever any source changes and the browser re-fetches, rather than polling on a timer.

  • A per-request profiler (GET /bootui/api/activity/request/{id}) that correlates one request's SQL, exceptions, security audit events, REST client calls, and cache accesses, served on every adapter by one shared engine assembler (ExecutionProfileAssembler). Correlation is tiered, strongest first: request id (the BootUI request id stamped on the signal), propagated (the request id, on work recorded in a task the BootUI agent propagated from the request to a JDK executor, when the task started within bootui.agent.executors.max-handoff of the request's end and the work within max-handoff of the task's start; reported unavailable unless the agent's executors sensor propagates for the application), trace id (a trace id exactly one captured request carries, on every adapter), serving thread (the request's servlet worker thread within its window, Spring MVC only), and time window (Spring MVC only, labelled approximate). Exceptions add the request method and path to the thread and window tiers; security events add the principal and are pinned to the serving thread when BootUI captured the audit event on it, so a concurrent request sharing the principal cannot trade events. SQL is matched exactly by trace id when present, otherwise exactly by the request's serving thread; it falls back to an approximate time-window match only when neither matched any statement (concurrent identical requests or async execution). On Spring MVC, exceptions keep their method, path, and window gate, within which a trace id or the serving thread decides first. REST client calls and cache accesses attach by trace id or serving thread only, like the stream. A signal attaches to at most one request: a trace id, thread, or window two captured requests could equally claim attaches the signal to neither and is counted in the notes. Each section reports its availability, the weakest tier it used, the tier of each shown child, and its total, truncated (above 200 entries), and ambiguous counts; correlationTiers lists the tiers the adapter can provide, with a reason for each one it cannot; and approximate flags a profile that used the time window. Every new field is additive. Repeated identical SELECTs above bootui.activity.n-plus-one-threshold are surfaced as a potential N+1, together with the distinct application call site(s) that issued them (from SQL Trace's call-site capture, bootui.sql-trace.capture-call-site, on by default) so a flagged group names exactly where in the code to look. Each correlated exception carries the exceptionGroupId of its Exceptions group, so a profile reaches the group's detail through GET /bootui/api/exceptions/{id} or get_exception_detail. The get_request_profile MCP tool and bootui request-profile <id> CLI command instead select the journal's retained request or scheduled/message execution profile first, and this HTTP-exchange DTO as fallback. An id neither retains is a tool error (CLI exit 1); source: "none" with available: false reports a journal that is off or an id that cannot be profiled.

  • Copy profile and Copy for AI in the profile drawer, and Copy for AI in an Exceptions detail, render one Markdown document through a single shared frontend helper, built only from DTOs the browser holds or loads through existing read endpoints, so identical DTOs produce identical text on every adapter. Captured strings are escaped, and code, stack traces, and SQL are fenced, so they cannot break the document. Copy for AI first shows the full document and lists what it leaves out (masked values, truncated or unavailable sections, withheld messages, details that could not be loaded). Preparing that preview reads each referenced exception group, or, from Exceptions, the Live Activity feed and the profile of the request the latest occurrence belongs to. The copy itself sends nothing and changes no state, the preview matches the clipboard exactly, and the document stays selectable when clipboard access is denied.

  • Optional durable persistence (bootui.activity.persistence.enabled, off by default, available on both adapters): in addition to today's in-memory-only default, captured entries can also be written to a SQL database over direct JDBC so history survives a restart and the dashboard can page back further than fits in memory. The design is a pluggable ActivityStore abstraction with two implementations:

    • InMemoryActivityStore — formalizes today's behavior (a bounded ring buffer) and remains the default; enabling persistence never removes or changes this default, it only adds a second store behind the same interface.
    • JdbcActivityStore — plain JDBC (no ORM), reusable against either the host application's own DataSource (the same one the SQL Trace panel may already be tracing) or a small dedicated, non-pooled connection configured through bootui.activity.persistence.dedicated-*. The backing table (bootui.activity.persistence.table-name, default bootui_activity) is created automatically on first use with a probe-then-create check that is safe when several instances start concurrently against the same schema. The SQL is portable except for two differences detected once per store from the JDBC driver's reported product name: 64-bit columns are NUMBER(19) on Oracle, which has no BIGINT, and pages are capped with LIMIT on MySQL and MariaDB instead of the SQL-standard OFFSET … FETCH FIRST. The store is tested against live PostgreSQL, MySQL, MariaDB, and Oracle servers. A BufferedActivityStore decorator wraps the JDBC store and provides, uniformly for any future ActivityStore implementation:
    • Write-behind buffering with a scheduled flush every bootui.activity.persistence.flush-interval (default 5s).
    • Merge-for-reads: a query is served from the in-memory buffer merged with the durable store, so recently captured entries are visible in the dashboard immediately, even before their next scheduled flush.
    • Re-queue on failure: entries from a failed flush are put back at the front of the buffer instead of being lost, and are retried on the next flush.
    • A flush guard (BootUiJdbcCaptureGuard) suppresses BootUI's own JDBC calls (create/probe/insert/select) from being recorded by the SQL Trace panel, preventing the store's own writes and reads from feeding back into the very activity feed being captured. Persisted rows are namespaced by an instanceId (bootui.activity.persistence.instance-id, defaulting to the HOSTNAME environment variable or a generated <app-name>-<random> id), so several BootUI instances — for example several replicas of the same application — can share one database table without seeing or pruning each other's rows (multi-tenant by partition key, not by separate tables). Rows older than bootui.activity.persistence.retention (default 7 days) are pruned periodically, scoped to the pruning instance's own rows only. When persistence is enabled, GET /bootui/api/activity additionally accepts q (free-text), until, cursor, and pageSize and serves pagination/filter/search from the durable store (a keyset cursor over (occurred_at, seq), stable under concurrent writes); the response's pageInfo.persistent flag tells the dashboard to page further back with pageInfo.nextCursor. KPIs, type counts, sources, and warnings are always computed from the live in-memory merge regardless of persistence — they summarize "right now", not whichever historical page happens to be browsed. Entries are masked before they are ever buffered or written, so persisted rows are immutable with respect to later masking-policy changes. The runtime journal's subscriber renders each recorded batch as the journal's feed renders it and stores each entry once (2.0.0 removed the 1.x poller of the merged feed and its capture-interval), remembering the ids it stored in a window of bootui.activity.persistence.buffer-max-entries that never forgets an entry still in the feed. Failed and slow entries are also remembered in a second window per entry type, which routine entries and other types never displace: records a failure-preserving buffer reserves, classified by the buffers' own rule and thresholds (engine ReservedActivityEntries: 5xx and slow requests, including a slow 4xx request whose severity is WARN; failed and slow statements; and failed, 4xx/5xx, and slow REST calls), and ERROR or SLOW entries of every other type. Such an entry that newer entries hid and that later reappears is therefore not stored twice, unless more remembered entries of its type than that window holds arrived meanwhile.

    On Quarkus, a dedicated QuarkusActivityCapture CDI bean (@Observes StartupEvent/ShutdownEvent) owns the journal capture's lifecycle that the Spring adapter instead wires inline in its controller constructor/shutdown(); the ActivityStore/BufferedActivityStore/JdbcActivityStore/ActivityStoreFactory engine machinery, every bootui.activity.persistence.* key, and the wire contract are identical on both adapters, and the ActivityStore and ActivityPersistenceSettings beans are always produced (persistence disabled is just enabled() == false, matching the Spring @ConditionalOnProperty default). One narrower, pre-existing divergence carries over: with the buffers feed source, Quarkus's baseline (persistence-disabled) feed has no server-side type/severity/since filtering — unlike Spring's separate LiveActivityService, the shared engine LiveActivityAssembler Quarkus's resource calls has none — so on Quarkus those filters take effect only once persistence is enabled and the query is served from the ActivityStore; the KPI strip stays computed from the full, unfiltered live merge either way on both adapters.

  • Runtime switch to a database, with no restart required: every GET /bootui/api/activity response carries a persistenceOption (whether persistence is currently active, whether a DataSource bean is present, and the configured table name), driving a "Currently saving N events in memory" tip and a "Use a database" disclosure next to the panel title whenever persistence is not yet active. If a DataSource is available, the disclosure offers a confirmation-gated "Use the existing datasource" action (POST /bootui/api/activity/use-existing-datasource, mirroring the confirmation UX of other state-changing actions such as Flyway migrate/clean or Cache clear) that atomically swaps the running instance's ActivityStore — behind a SwitchableActivityStore indirection — from InMemoryActivityStore to a BufferedActivityStore/JdbcActivityStore pair: it verifies/creates the backing table against the current DataSource, checks that the store's paged read runs there, and starts the same journal capture and flush cycle a startup-enabled instance would have, with no restart and no dropped entries. If no DataSource is present, the disclosure instead links to setup documentation for configuring one (or a dedicated one) and enabling persistence at startup. The switch is runtime-only: it does not write configuration, so a later restart reverts to the in-memory default unless persistence is also turned on via bootui.activity.persistence.enabled=true. Identical on both adapters (Spring's LiveActivityController and Quarkus's LiveActivityResource share the same engine-level ActivitySwitchService).

Acceptance criteria:

  • The panel's reads inherit the loopback filter, Host allow-list, cross-site write defenses, and value masking from the underlying sources; the "Use the existing datasource" switch is a state-changing action allowed only when neither the app nor the Live Activity panel is read-only, gated by the same explicit confirmation used by other destructive actions (Flyway migrate/clean, Liquibase update, Cache clear).
  • Sources that are absent or disabled (through their own bootui.panels.* toggles) simply drop out of the stream; when no source is available the panel returns a stable unavailable report.
  • Exact request/execution-id and unambiguous trace-id matches are distinguished from approximate fallback correlation; unavailable tiers and unattributed work stay explicit.
  • With bootui.activity.persistence.enabled=false (the default), behavior, response shape, and the merged in-memory feed are unchanged from before persistence existed; no additional bean, thread, or connection is created.
  • With persistence enabled, the backing table is created automatically if absent, entries survive a restart, a failed flush never loses entries, and BootUI's own persistence-related JDBC traffic never appears in the SQL Trace panel or feeds back into the Live Activity stream.
  • The "Use the existing datasource" switch takes effect immediately (no restart), returns a clear error when no DataSource is present or the request is unconfirmed, and is a no-op (not an error) when persistence is already active; it never blocks on a hung schema check indefinitely (the same bounded JDBC timeouts the startup path uses).
  • SCHEDULED capture is implemented on both adapters. Quarkus's scheduler (io.quarkus.scheduler.Scheduler) has no per-execution observability hook analogous to Spring's ScheduledTaskObservationContext; instead QuarkusScheduledTaskRunRecorder observes the CDI SuccessfulExecution/FailedExecution events the scheduler always fires, gated on the SCHEDULER capability (quarkus-scheduler is a provided-scope, R2-excluded dependency, mirroring QuarkusSecurityEventCapture).

5.14.2.1 Live Flow service map (Live Activity mode)

Purpose: answer "what external systems does this running application depend on, and what evidence has BootUI recently observed for each relationship?" without introducing distributed tracing infrastructure, network discovery, or active health probes. Delivered as a second mode of the Live Activity panel, not as a separate panel, because it is a second reading of the same already-captured evidence.

Contract: GET {api}/activity/service-map on Spring MVC, Spring WebFlux, and Quarkus, returning ServiceMapReport (available, unavailableReason, generatedAt, application, nodes, edges, truncation, sources, warnings). Because it lives under /activity, the Live Activity panel's own enable/read-only policy and the shared localhost/Host/cross-site-write guard already cover it with no extra registration.

Data sources — reused only, never newly instrumented:

  • Completed inbound requests from the HTTP Exchanges buffer, folded into one generic INBOUND lane. Per-caller nodes are deliberately not derived: a remote address is neither a stable identity nor safe to display here.
  • Outbound HTTP calls from the REST Client recorder, grouped to a scheme://host[:port] origin.
  • Configured JDBC pools from the Connection Pools service. The map independently strips JDBC authority user-info, Oracle driver-style credentials, and driver parameter tails even when full value exposure is enabled.
  • Retained JDBC statements from the SQL Trace recorder, reduced to their coarse category.
  • Kafka producer records grouped by topic and RabbitMQ publisher records grouped by exchange/routing destination. Consumed records and messages are inbound work and are never modelled as outbound dependencies.
  • Cache accesses (HIT/MISS/PUT/EVICT/CLEAR) from the same CacheActivityRecorder the Cache panel and Live Activity's CACHE entries already read, grouped by the safe cache-manager/cache-name identity — never the accessed key or value. Gathered only when the Cache panel is enabled, the recorder is itself capturing, and at least one CacheManager was successfully instrumented, on Spring MVC and Spring WebFlux; Quarkus has no comparable interception seam (see docs/QUARKUS-SUPPORT.md) and always reports cacheAvailable: false here, exactly as it does for the Live Activity feed.

Assembly is framework- and JSON-free (ServiceMapAssembler in bootui-engine); each adapter only gathers evidence from beans it already owns and passes a neutral ServiceMapSources record, so all three runtimes serve a byte-identical contract.

Interpretation rules:

  • configured and observed are reported separately on every node and are never collapsed, so absence of traffic is never presented as absence of a dependency. Cache dependencies are always configured: false in this contract: only observed capture evidence feeds them today, the same honesty rule Kafka and RabbitMQ dependencies already follow.
  • outcome is one of NO_EVIDENCE, OBSERVED_OK, or RETAINED_FAILURES, and describes retained evidence only. It is never a health check of the remote system. A cache MISS is never counted as a failure — it is a normal, expected outcome — so a cache dependency's outcome can only ever be NO_EVIDENCE or OBSERVED_OK.
  • Statement evidence is attributed to a pool only when exactly one pool is configured and exactly one traced datasource has a matching name. Otherwise statements are summarized on a separate aggregate node, the pools stay configured-only, and the reason is surfaced as a warning. A statement-to-pool relationship is never fabricated.
  • distinctOperations is null where the source cannot report one honestly rather than defaulting to a meaningless count.
  • Evidence whose identity cannot be reduced safely is omitted with a warning, never shown under a guessed identity.

Flow correlation:

  • ServiceMapInteractionDto.flowId is a nullable, opaque, one-way SHA-256-derived identifier (ServiceMapIdentities.flowId) computed from whatever distributed-trace id was active when an inbound HTTP request, a SQL statement, an outbound REST call, or a cache access completed. Interactions sharing the same trace id — the same request's actual path through the application — share the same flowId, so the client can recognize and sequence them as one causal flow; the raw trace id itself never reaches this contract, and a blank/absent trace id yields flowId: null rather than a synthetic one. Kafka and RabbitMQ interactions carry no trace id at capture time and are therefore never correlated into a flow — they remain exactly as uncorrelated here as everywhere else in BootUI's Live Activity model.

Bounds and motion:

  • Dependencies are capped at 28 and each edge carries at most 6 retained interactions. Configured dependencies rank ahead of purely observed ones so a burst of one-off origins cannot push a declared database off the map. Any omission is reported through truncation and a warning.
  • The client lays the map out as a bounded hybrid left-to-right topology: inbound HTTP lane, central application hub, then an airy right-facing fan through six dependencies or a two-column rack above that threshold. The fan uses a fixed 288-pixel radius and 72-pixel vertical pitch, producing an 800–844-pixel-wide typical map with readable connector travel. The dense rack increases the application gap to 72 pixels, column gap to 32 pixels, and row pitch to 72 pixels; it is bounded at 1,040 logical SVG pixels wide and 1,046 pixels tall at the 28-dependency cap inside the stage's scroll area. Fan connectors use smooth cubic paths; dense routes use deterministic shortest clear paths around node rectangles. Every pulse and slow trail uses CSS Motion Path with the visible connector's exact path.
  • ServiceMapInteractionDto.id is derived from the originating buffer's monotonic sequence, so it is stable across refreshes. The client animates a short particle only when a stable edge (present in both the previous and the next snapshot) carries an interaction id the previous snapshot did not. A first load, a newly appearing dependency, and an idle application therefore produce no motion at all. Bursts are coalesced to a small per-edge count and a hard concurrent cap rather than queued.
  • When freshly animated interactions share a non-null flowId, the client sequences their motion into a causal story rather than animating unrelated-looking simultaneous blips: an inbound-HTTP pulse always starts first, and downstream pulses start only once that inbound pulse would have finished arriving at the application. Downstream evidence then replays in ascending retained completion-time order; cache precedes JDBC/outbound HTTP only as a deterministic same-millisecond tie-break, so the UI never invents an order the completed evidence does not support. Further same-flow downstream pulses are staggered by a small, bounded step so a fan-out still reads as distinguishable beats rather than one instant flash. A downstream pulse whose batch carries no retained inbound pulse for its flow — the common case once the inbound leg has already scrolled out of the retained tail — fires immediately rather than waiting for an inbound arrival that batch will never carry, and a pulse with no flowId is never delayed at all. Sequencing only ever paces when already-completed evidence is shown; it never delays a pulse's underlying evidence from appearing at all, and the animation queue's existing concurrency cap and per-edge cap apply identically whether or not a pulse happens to be sequenced.
  • Slow interactions pulse a calm, unmistakable amber with a restrained trailing halo — 1200–1500ms, longer than a normal completion (650–850ms) or a failure (900–1100ms) — so timing itself, not color alone, carries the "slow" meaning. During exactly the same delayed animation window, its causal target carries a temporary amber ring and a SLOW · <duration> chip; failure uses a temporary red ring and ERROR chip. Inbound evidence targets the application, while outbound evidence targets the remote dependency; overlapping failure takes visual precedence over slow without clearing the slow window early. Retained failures never permanently color topology nodes or edges — they remain in counts, details, recent rows, accessible text, and source links. No pulse flashes, bounces, loops, or drifts: each plays exactly once, linearly, and a sequenced pulse and its target signal stay hidden for their entire causal delay. Motion uses CSS offset-path/offset-distance, whose delay starts when the dynamic pulse mounts in the supported Chromium browser, rather than SMIL begin timestamps tied to the document timeline.

Acceptance criteria:

  • Rendering the map performs no network call, probe, DNS lookup, connection attempt, scan, or new interception, and adds no instrumentation.
  • No secret, remote HTTP path/query value/user-info/fragment, message payload, message key, cache key/value, SQL text, bound parameter, unmasked JDBC credential, or raw distributed-trace id reaches the response.
  • Dependencies are grouped by their complete sanitized identity. Public node ids are stable SHA-256-derived opaque values, while only display labels are truncated, so long identities with a shared prefix remain separate.
  • Evidence from a source panel that is disabled or unavailable on the running adapter never reaches the map, and when no source is available the report is available: false with a clear reason rather than an empty graph.
  • The rendered graph is bounded before serialization and every omission is visible.
  • The map is usable by keyboard and screen reader (focusable nodes, arrow-key traversal, an accessible detail view, and a hidden textual list of every node and relationship) and honors prefers-reduced-motion by replacing motion with a brief, immediate static target/edge highlight (never delayed or sequenced) plus a polite live-region update that narrates slow/failure duration and a sequenced flow's complete causal story in one sentence.
  • Pausing cancels every pulse, target state, reduced-motion highlight, announcement, and associated timer. A response already in flight may refresh the retained report while paused, but becomes the new comparison baseline without scheduling or replaying its evidence after resume.
  • Spring MVC, Spring WebFlux, and Quarkus serve the same shape, verified by the shared conformance suite.

5.14.2.2 Runtime Insights Panel

Runtime Insights (runtime-insights, Home group, after Live Activity) projects the runtime journal's retained events into observations on read (PLAN-v2.md §5.4, §5.5). Opening it starts no capture, scan, database read, or network call. Its one action is Profile resources, an opt-in JFR session the developer starts, which bootui.panels.runtime-insights.read-only or bootui.read-only blocks.

  • GET /bootui/api/runtime-insights returns the window, per-source correlation coverage, every check with its status (EVALUATED, INSUFFICIENT when no work was eligible, PARTIAL, NOT_APPLICABLE with its reason, or UNAVAILABLE with its reason when it reads SQL that this application's stack does not record, such as R2DBC), the observations, and limitations. The projection is cached until the journal records more or a panel's enablement changes. Eligibility follows the observation's unit, not just its request count: a heap-growth check that examined collections is evaluated even with zero requests, whether it observed growth or a stable heap. Each kind's external validation (recorded once in the engine's ExternalValidation) decides whether its rows are listed by default, and is never exposed: no check, row, limitation, MCP or CLI text names it or a plan identifier, which UserFacingPlanJargonTests and the UI's userFacingText.test.js enforce. PASSED (errors-behind-2xx, changed-code-not-executed) and NOT_VALIDATED (kinds silent or never exercised on the validation applications) are listed by default; FAILED (route-time-breakdown, exception-hotspots, connections-per-request, ai-usage-by-route, each folded into the panel showing the same evidence, which links to its rows), UNDER_SAMPLED (repeated-selects, lazy-sql-after-handler, split-transaction-writes, framework-warnings-by-route, anonymous-data-reach), NOT_LISTED (gc-inflated-latency, heap-growth-after-gc, reached from the Memory panel), and NOT_JUDGED (any kind added after the external validation) are not. Every observation carries listed, whether the panel's and the agents' default list shows it, and, when it does not, unlistedReason: for a kind that is not listed, where its evidence is shown (the panel it is folded into, the Memory panel, its own panel, or the full list, a search, or a query naming it), or else the kind's own rule. Within the kinds, a route-time-breakdown is prominent with a warm median of 20 ms or more, authorization taking 20 % of the warm time, or a median of 50 authorization decisions a request; exception-hotspots collapses the groups seen only behind 4xx responses into one counted row, unless (nearly) every request to their route, at least three, recorded them, and those caught in completed scheduled runs or messages into another; repeated-selects leaves to lazy-sql-after-handler a statement that kind reports on the same route when every affected request repeated it after the handler returned and the lazy row names each of its call sites, its check's reason counting them, and names the render-time call site of statements run after the handler; and framework-warnings-by-route leaves out a WARN without a specific check and a 4xx-only Resolved [...], and counts in one row the framework ERROR events that carried no request id. The panel's Show all routes, a search, or a deep link lists the rest.
  • GET /bootui/api/runtime-insights/insights/{id} returns one observation's evidence: at most 20 rows and the count left out. Ids are kind:hash, stable across refreshes and restarts.
  • GET /bootui/api/runtime-insights/impact?symbol=<symbol> resolves a route, a bean, a class's simple name, a repository, a table, a cache, a host, or an application event type to exactly one node of the runtime model (PLAN-v2.md §5.7), or answers AMBIGUOUS with up to 8 candidates, NOT_FOUND, or UNAVAILABLE when the beans cannot be read. A symbol prefixed with its kind, such as TABLE sample_products or a candidate as listed, names exactly that node. A resolved symbol lists, 8 rows each with totals, the routes that reach it and ran in this run, the mapped routes that reach it and did not, and the routes outside its reach that use a table, cache, or host the routes through it touched, with the structural reach as a count. A route is its own impact: it is listed itself, with the routes sharing what it touched. A known handler method (Class#method, with # URL-encoded as %23) narrows the answer to that method's mapped routes (observedFrom=HANDLER_MAPPING); the class alone still includes the whole bean (observedFrom=STRUCTURE). With the BootUI agent's code-paths sensor, any application method is a symbol: Class#method, Class.method(...), or METHOD fq.Class#method, with Java parameter types or a JVM descriptor naming one overload (PLAN-v2.md §5.17). Its observed routes (observedFrom=ROUTE_TREES) are those whose requests' own call trees ran it, at any depth, from each route's tree amended by late fragments, or Code Inventory's first-request route, never composed from INVOKES edges; each carries executedRequests and partial. methods lists the method keys it names, methodStatus what Code Inventory says, and notObserved the routes reaching its bean that ran without their trees showing it, with why that proves nothing; such a route is in notExercised only when Code Inventory saw every overload never run in this run. Whole-run route counts survive event eviction; when aggregate route counts overflow, an unknown route is not called unexercised, and notExercisedUndetermined=true marks the list incomplete. Disabled source-panel facts are omitted and named as limitations only when relevant evidence was recorded; when HTTP Exchanges is disabled the answer is unavailable. It is worded as what was and was not exercised, never as safe, and reads only what the journal, the aggregates, and the bean and mapping providers already hold.
  • GET /bootui/api/runtime-insights/impact/symbols?query=<text> suggests, as the impact box is typed in, at most 20 of the routes, beans, repositories, tables, caches, hosts, and events of the run's model whose name or bean class contains the text, with their kind and class and the total matched: an exact name first, then a name or a route's path starting with it, then a class starting with it. It reads only the run's model, and answers available=false with the reason when the journal is disabled.
  • GET /bootui/api/runtime-insights/comparison[?run=<runId>] compares the current run with the newest kept run, even one that served no HTTP request, or the chosen one (PLAN-v2.md §5.8). Its status is COMPARED, INSUFFICIENT when no route served 3 requests in both runs, NOT_COMPARABLE with the database, profile, or cache difference first, NO_PREVIOUS_RUN with the reason, or UNAVAILABLE. Behavior rows come first (statements, REST and AI calls, cache misses, and tokens per request; new statements, exceptions, and routes; status-class shares; allocation), then the runtime model's added and removed edges, the restart cost compared only between two restarts, and the warm latency last, labelled noisy. Each list holds at most 200 rows. codeChanges, shown first, lists with the BootUI agent the methods Code Inventory found changed or added since the previous run, not run yet first, each with its status and the routes whose call trees ran it, with the change counts, removed methods counted only. It is null without the agent, and available=false with the reason while the inventory sensor or the Code Inventory panel cannot answer, or against a run other than the previous one. sideEffects, next, lists with the agent the side-effect keys (sensor, kind, masked target, and route, execution, or startup owner) new, gone, or whose owner this run did not exercise, from the network, files, processes, and environment sensors, each sensor COMPARED, PARTIAL, or NOT_COMPARED with the reason when it did not record the whole of both runs; null without the agent.
  • GET /bootui/api/runtime-insights/resource-profile returns the Profile resources session (PLAN-v2.md §5.11): IDLE, RUNNING, COMPLETED, FAILED, or UNAVAILABLE with the reason, such as a runtime without JFR or a journal that does not record the resources source, and the last session's results. Reading it starts nothing. POST /bootui/api/runtime-insights/resource-profile starts a JFR session of bootui.resources.jfr.max-duration, joining a running one, and POST /bootui/api/runtime-insights/resource-profile/stop ends it early. The results count CPU samples and JFR's estimate of allocated bytes per route, virtual threads included, with each route's five hottest frames, at most 20 routes.
  • Copy for AI on the open observation previews one Markdown document from the detail already loaded (sentence, checks, requests to open, evidence, and what it cannot see) and sends nothing. The get_runtime_insights, get_runtime_insight, get_runtime_impact, and get_runtime_run_comparison MCP tools (bootui insights list, show, impact, and compare) return the same facts compacted for agents (PLAN-v2.md §5.6).
  • notExercised lists, up to 100, the declared application routes no request of this run reached, counted from the run's aggregates so an evicted request still counts; framework endpoints and catch-all patterns are left out.
  • Live Activity links to the slowest route's breakdown under its KPIs, and a request's drawer loads Why this route is slow on demand. Export JSON saves the report client-side.
  • Each observation carries a status (OBSERVED, INSUFFICIENT, or PARTIAL), one sentence naming what was counted, the eligible and affected request counts, the correlation tier it relies on, one to three conditional checks, up to three exemplar request ids, and limitations. No severity, score, or Scorecard contribution.
  • Available on every stack while bootui.runtime-journal.enabled is true, and unavailable otherwise with that property named. Stack-specific gaps are reported per observation, never by hiding it.

5.14.3 Traces Panel

Purpose: show distributed-trace waterfalls captured locally, so a request that fans out across cooperating local services can be read as a single trace during development.

Data sources:

  • The local, bounded, in-memory trace store fed by BootUI's OTLP receiver and its own in-process capture. On Spring Boot, BootUI runs an embedded OTLP/HTTP receiver at POST /bootui/api/otlp/v1/traces; on Quarkus, spans are captured in-process via quarkus-opentelemetry (no receiver). Retention, per-trace span caps, and self-span exclusion are governed by bootui.telemetry.*.

Aggregator topology:

  • BootUI supports a dev-time "one aggregator" pattern for cross-service traces: each cooperating local service exports its OpenTelemetry spans (OTLP/HTTP) to a single BootUI instance's /bootui/api/otlp/v1/traces endpoint, which becomes the aggregator. Because every service propagates W3C trace context, spans sharing a trace id are stitched into one cross-service waterfall in that aggregator's Traces panel. This is a dev-only sink, bounded in memory, and never forwards data off-process (see §2.3 non-goals).

BootUI span enrichment:

  • When bootui.telemetry.enrich is on (default, effective only while bootui.telemetry.enabled is on), each service's BootUI stamps a stable bootui.* attribute vocabulary onto its own spans, so the aggregated cross-service waterfall carries genuine BootUI depth per service:
    • Identity attributes are stamped on span start by an OpenTelemetry SpanProcessor: bootui.enriched=true, plus the service name and instance id, so every enriched span is attributable to the BootUI that produced it.
    • Depth attributes are stamped at BootUI's existing capture points on the currently-active span (an OTel SpanProcessor cannot mutate a span in onEnd, which is read-only): the SQL Trace recorder increments bootui.sql.queries per statement and sets bootui.sql.n_plus_one when the same grouping/threshold logic the Live Activity profiler uses flags a suspected N+1; the exception store stamps bootui.exception.type and increments bootui.exceptions when it captures an exception. These reuse the same active-span/trace-id plumbing Live Activity already relies on, so no new correlation model is introduced.
  • The enrichment contract is framework-neutral: a SpanEnricher seam in bootui-engine (with a no-op default) is implemented once by the OTel-touching adapter code, kept concentrated behind the engine's ArchUnit boundary rule. The DTO shape is unchanged — bootui.* attributes flow through the generic span attribute list — and the Traces panel surfaces a "BootUI-enriched" indicator plus the bootui.sql.* / bootui.exception.* / service attributes in the trace drawer.

Acceptance criteria:

  • The panel's reads inherit the loopback filter, Host allow-list, cross-site write defenses, and value masking from the telemetry store; clearing retained traces is gated by bootui.panels.traces.read-only.
  • With bootui.telemetry.enrich=false, no bootui.* attributes are stamped and the enrichment indicator does not appear; trace capture and the waterfall are otherwise unchanged.
  • Enrichment never forwards data off-process and adds no unbounded state: per-span running counters are held in a bounded structure and the no-op enricher path (telemetry or enrichment disabled) pays nothing.

5.14.4 Email Panel

Purpose: capture outgoing application email for local inspection — a high-value dev-loop aid with no built-in Spring equivalent.

Data sources:

  • A BeanPostProcessor wraps every JavaMailSender bean with a decorator that parses each send(...) call (recipients, subject, text/HTML bodies, attachment metadata) into the framework-neutral EmailCaptureService/EmailStore before delegating to the real sender — pass-through by default, so application behaviour is unchanged.
  • An optional, explicitly opt-in bootui.email.dev-trap=true mode records messages without ever handing them to the real sender (a MailDev/GreenMail-style trap), off by default so BootUI never silently swallows application mail.
  • On Quarkus, a CDI observer of the SentMail event quarkus-mailer fires after each send feeds the same EmailCaptureService. There is no dev trap there; Quarkus's own mock-mail mode decides whether mail is sent.

Acceptance criteria:

  • Available only when a JavaMailSender bean is present (e.g. spring-boot-starter-mail) on Spring, or when quarkus-mailer is present on Quarkus; otherwise the panel reports a clear unavailable reason instead of an empty list.
  • Recipients, subject, and body text are revealed by default, because email content is ordinary application data rather than a credential. With bootui.email.mask-content=true they are masked and only revealed under bootui.expose-values=FULL; attachment metadata (name/type/size, never contents) is never masked since it carries no message content.
  • Messages are listed newest-first from a bounded ring buffer sized by bootui.email.max-entries (default 100, oldest evicted first); a message's HTML body renders in a sandboxed iframe (no script execution, no same-origin access) and each message can be downloaded as a .eml file.
  • Each captured text/HTML body is additionally truncated at bootui.email.max-body-length characters (default 200,000) so one oversized message cannot spike memory before the entry-count cap would evict it; attachment content is never captured in the first place (metadata only), so no equivalent cap applies there.
  • Clearing the buffer is gated by bootui.panels.email.read-only, consistent with every other clearable capture panel.

5.14.5 REST Client Panel

Purpose: show outbound HTTP calls the application makes through its own REST clients, so a slow or failing downstream dependency is visible without a third-party HTTP proxy library.

Data sources:

  • A shared ClientHttpRequestInterceptor customizes every auto-configured RestClient and RestTemplate; a shared ExchangeFilterFunction customizes every auto-configured WebClient. Each call is recorded with its method, host, path, query string, response status, wall-clock duration, success/failure, client type, trace id (when active), executing thread, and call site when the interception stack still exposes it.
  • On Quarkus, the supported MicroProfile RestClientListener SPI conditionally registers a metadata-only JAX-RS client request/response filter on every REST Client Reactive proxy when Capability.REST_CLIENT_REACTIVE is present. The optional listener is excluded and its service-provider entry omitted when the capability is absent.

Acceptance criteria:

  • A capture failure never disrupts the outbound call itself: every adapter instrumentation point always lets the request through and only best-effort record around it.
  • Spring masks retained query/header values by name and captures request headers only when bootui.rest-client-trace.capture-headers=true. Quarkus is always metadata-only: it never reads or retains bodies, arbitrary headers, credentials, cookies, or tokens, and removes URI user-info/fragments plus sensitive path/query values before storage.
  • Every adapter shares one URI masking policy with HTTP Exchanges: authority user-info credentials are removed before storage and never revealed by any exposure setting, sensitive parameter names are matched percent-decoded, and a query-shaped fragment is masked like a query string.
  • The client error message is flattened, credential-redacted (URL credentials plus sensitive query values echoed by the exception), and truncated before it is buffered; value exposure METADATA_ONLY hides it entirely, with the failure still visible through the call's success flag and status.
  • Calls are grouped by method, host, and normalized path (numeric/UUID segments collapsed to {id}) and a group at or above bootui.rest-client-trace.chatty-call-threshold is flagged as a chatty (repeated-call) pattern, for calls of any HTTP method.
  • Pause/Resume and Clear are gated by bootui.panels.rest-client-trace.read-only; recording state, buffer size, and the slow/chatty thresholds are configurable under bootui.rest-client-trace.*.
  • Retention is failure-preserving: bootui.rest-client-trace.reserved-share-percent (default 25) of the buffer is reserved for the most recent failed, 4xx/5xx, and slow calls, routine calls are evicted first, and the report's retention object states the kept, reserved, and evicted counts.
  • Recent calls surface in Live Activity as REST_CLIENT entries. Every stack first matches the stamped request or execution owner; an unambiguous trace id supplies another exact tier. Quarkus and WebFlux never infer ownership from an event-loop thread. Each profile/report states its supported tiers and leaves unplaceable work unattributed.
  • The dedicated panel is available on Spring MVC, Spring WebFlux, and Quarkus. Quarkus keeps it visible whenever the optional capability is present (proxies are initialized lazily), renders a no-proxy message until instrumentation occurs, and refreshes via its JAX-RS SSE stream. On WebFlux only WebClient instances are instrumented.
  • On the Spring adapters, /bootui/api/panels additionally requires that at least one RestClient, RestTemplate, or WebClient has actually been instrumented (mirroring how Kafka/Email/Cache report against their own beans): an application that never builds one of the three reports the panel unavailable rather than available-with-an-empty- buffer, since the recorder bean backing the panel is registered unconditionally and so is never itself a useful signal. On Quarkus, real 4xx/5xx responses are recorded as transport-successful error responses; a pre-response transport failure is reported to the filter with status 0 and stored as a failed call with no invented HTTP status.

5.14.6 Kafka Panel

Purpose: show producer/consumer activity over KafkaTemplate/@KafkaListener (Spring) or SmallRye Reactive Messaging Kafka channels (Quarkus) as its own dedicated, filterable view, over the same capture that already feeds Live Activity's MESSAGING entries.

Data sources:

  • On Spring, KafkaProducerCaptureBeanPostProcessor and KafkaConsumerCaptureBeanPostProcessor wrap every application-owned KafkaTemplate bean and @KafkaListener container factory, pass-through by default. On Quarkus, QuarkusKafkaProducerCapture/QuarkusKafkaConsumerCapture implement SmallRye Reactive Messaging's OutgoingInterceptor/IncomingInterceptor SPI. Both feed the same framework-neutral KafkaActivityRecorder (bounded ring buffer in bootui-engine) that already backs Live Activity's MESSAGING entries — there is only ever one buffer, so the dedicated panel and Live Activity are always in sync and clearing one clears both.

Acceptance criteria:

  • Available whenever a KafkaTemplate bean is present on Spring, or the Quarkus KAFKA capability is present in a non-production launch; configured channels determine whether Quarkus records activity but do not gate panel availability. Otherwise the panel reports a clear unavailable reason instead of an empty list.
  • Only metadata is ever captured — direction (PRODUCE/CONSUME), topic, partition, offset, duration, success/failure, consumer group id when the adapter exposes it (Spring), and listener/channel id. The message value/payload is never captured at all, regardless of configuration, since it is an arbitrary, potentially large application payload with no generic masking strategy (unlike a SQL statement or a config value). Raw exception messages are never retained; failure text is generic.
  • The message key is never retained verbatim: when bootui.kafka.capture-key=true (the default), a SHA-256 hash of the key is captured and truncated to bootui.kafka.max-key-length hex characters; when disabled, the key is null.
  • Messages are listed newest-first from a bounded ring buffer sized by bootui.kafka.max-entries (default 200, oldest evicted first).
  • Clearing the buffer is gated by bootui.panels.kafka.read-only, consistent with every other clearable capture panel; disabling capture entirely (bootui.kafka.enabled=false) stops both the panel and Live Activity's MESSAGING entries, not just the dedicated view. Disabling the Kafka panel also stops its underlying capture.
  • Ships on both Spring (servlet and WebFlux — the controller has no reactive-specific code) and Quarkus.

5.14.7 RabbitMQ Panel

Purpose: show payload-free RabbitMQ publish/consume activity over Spring AMQP or SmallRye Reactive Messaging, using the same bounded capture that feeds Live Activity's MESSAGING entries.

Acceptance criteria:

  • Spring capture composes with existing RabbitTemplate before-publish processors and listener-factory advice; Quarkus capture is registered only when quarkus-messaging-rabbitmq is present and excluded otherwise.
  • The message body and arbitrary headers are never captured. Exchange, routing key, and queue metadata are length-bounded; raw exception messages are not retained.
  • Correlation IDs are omitted by default. With bootui.rabbitmq.capture-correlation-id=true, only a truncated SHA-256 hash is stored.
  • The in-memory buffer is capped by bootui.rabbitmq.max-entries, oldest-first eviction, and clear is gated by bootui.panels.rabbitmq.read-only. Disabling the RabbitMQ panel also stops its underlying capture.
  • Spring panel availability requires a RabbitTemplate bean. Quarkus availability requires the RabbitMQ messaging extension in a non-production launch. Dependency absence must not link optional RabbitMQ classes or advertise capture.
  • Quarkus preserves the shared wire contract but leaves producer exchange, consumer queue, and producer duration unavailable because the SmallRye callbacks do not expose them.

5.14.8 JMS Panel

Purpose: show payload-free Spring JMS producer/consumer activity over the same independent bounded capture that feeds Live Activity's MESSAGING entries.

Acceptance criteria:

  • Spring MVC and WebFlux wrap application-owned JmsTemplate and AbstractJmsListenerContainerFactory beans while preserving converters, callbacks, listener dispatch interfaces, and error handlers. Direct lower-level JMSContext/MessageProducer/MessageConsumer calls remain outside this Spring integration seam.
  • Only bounded metadata is captured: direction, sanitized destination, duration, success/failure type, subscription, listener id, and—by default—a truncated hash of the provider-assigned message id (opt out with bootui.jms.capture-message-id=false). Payloads, arbitrary headers/properties, raw message ids, provider-specific destination strings, and exception messages are never retained.
  • The independent buffer is capped by bootui.jms.max-entries; disabling bootui.jms.enabled or the JMS panel stops capture without affecting Kafka or RabbitMQ retention.
  • GET /bootui/api/jms lists entries newest-first and supports destination, message-id, subscription, listener, failure-type, and direction filtering. Confirmation-gated DELETE /bootui/api/jms clears the shared panel/Live Activity buffer and is blocked by bootui.panels.jms.read-only.
  • Spring availability requires a JmsTemplate bean and reports unavailable in a GraalVM native image because runtime class proxies cannot be generated. Quarkus reports the panel not yet available and directs users to Kafka or RabbitMQ.

5.15 Profile Diff Panel

Purpose: show which properties are contributed by active profile-specific property sources.

Data sources:

  • Spring ConfigurableEnvironment property sources.

Features:

  • List active profiles.
  • Group enumerable profile-specific property sources by profile.
  • Mask secret-like property values.
  • Filter profile properties.

Acceptance criteria:

  • Secret-like keys remain masked by default.
  • Metadata-only exposure hides values.
  • Source attribution remains visible.

5.16 Spring Security Panel

Purpose: answer "Which security filter chains and authorization rules apply?"

Data sources:

  • Spring Security FilterChainProxy (Spring MVC) or ordered SecurityWebFilterChain beans (Spring WebFlux).
  • Authentication provider and user-details-service beans.
  • Spring MVC or annotated WebFlux request mappings when available.

Features:

  • List filter chains, matchers, filter pipeline, CSRF/CORS/session indicators.
  • Summarize authentication provider and user-details-service types without credentials.
  • Best-effort explain for a method/path.
  • Best-effort per-endpoint authorization rule listing.

Acceptance criteria:

  • The panel is classpath-gated and unavailable when Spring Security Web is absent.
  • Credentials, password hashes, signing keys, session IDs, and tokens are never displayed.
  • Matching caveats are clearly marked as best-effort.
  • BootUI's own security chains and endpoint rules are hidden by default.

5.17 Spring Data Explorer

Purpose: answer "Which Spring Data repositories does this app declare, against which store, and what queries do they expose?"

Data sources:

  • Spring Data RepositoryFactoryInformation beans discovered in the application context.
  • Each repository's RepositoryInformation (domain type, ID type, repository interface, custom implementation class, query methods, fragment methods).

Features:

  • List detected Spring Data repositories, grouped by store module (JPA, JDBC, MongoDB, R2DBC, Redis, Cassandra, Neo4j, Elasticsearch, Couchbase, Spring Data Commons, generic).
  • For each repository, show:
    • Repository interface name and package.
    • Domain type and ID type.
    • Custom implementation class, if any.
    • Method list with origin badge (CRUD, derived-query, @Query, fragment, default-method).
    • For @Query-annotated methods: the declared query string, native flag, and named-query reference if any.
  • Filter by repository interface, bean name, domain type, method, or query content.

Out of scope for the current release surface:

  • Executing repository methods or arbitrary queries from the UI.
  • Schema migration controls.
  • Editing or generating repository code.

Acceptance criteria:

  • When Spring Data is not on the classpath, the API endpoint is not registered.
  • When Spring Data is present but no repositories are detected, the panel shows a clear empty state.
  • Query strings declared via @Query are displayed verbatim; BootUI never rewrites or executes them.
  • No repository method is invoked as a side effect of opening the panel.

5.17.1 Hibernate Panel

Purpose: answer "Which Hibernate/JPA mapping and configuration risks should I review before they become production performance issues?"

Data sources:

  • JPA EntityManagerFactory metamodel when Hibernate ORM is present.
  • Spring Data repository metadata for query and paging heuristics.
  • Selected Spring Boot and Hibernate configuration properties.

Features:

  • Run explicit, read-only Hibernate/JPA checks against the host application's mapped entities.
  • Report findings by severity and category, including fetching, identifier, configuration, repository-query, cascade, and cache risks.
  • Show scanned entity packages, rule counts, mapped-entity counts, sample evidence, and remediation guidance.
  • Expose a diagnostics array (source, unit, level, message) with one entry per failed rule evaluation, per rule/unit evaluation whose required evidence was unavailable, and per discovery gap, plus a nullable per-result coverageNote for findings from partly evaluated rules. Levels are ERROR (failed), WARNING (evidence unavailable) and INFO (advisor limit by design). At most 200 entries are returned; when capped, the last is a source: "diagnostics" entry stating the omitted count, and every rule and distinct discovery reason keeps at least one entry, failures first. Diagnostics are never counted as findings.
  • Cache the latest report until the next explicit scan.

Availability:

  • The advisor scan requires JPA/Hibernate ORM on the classpath and a resolvable entity metamodel.

Out of scope for the current release surface:

  • Executing SQL, invoking repositories, changing mappings, or rewriting queries from the UI.
  • Replacing project-specific performance tests, query plans, or code reviews.
  • Live runtime session/query-cache statistics: see the standalone Hibernate Statistics panel (Database section, docs/SPECIFICATION.md § "Hibernate Statistics Panel" below).

Acceptance criteria:

  • When Hibernate/JPA infrastructure is unavailable, the panel shows a clear unavailable state.
  • Opening the panel never executes application queries or mutates persistence metadata.
  • The scan action is blocked by global read-only mode and bootui.panels.hibernate.read-only.
  • Findings are presented as heuristic review prompts, not definitive performance verdicts.

5.17.1.1 Hibernate Statistics Panel

Purpose: answer "What is Hibernate's SessionFactory doing right now — session/transaction volume, entity and collection activity, query execution, and cache effectiveness?"

This is a standalone, Database-section runtime-monitoring panel, separate from the Hibernate Advisor panel above: it reports live counters rather than static findings, so it does not fit the advisor's "run checks, report findings" workflow.

Data sources:

  • org.hibernate.stat.Statistics, read from the resolved SessionFactory.

Features:

  • Displays a live snapshot of Hibernate's own runtime Statistics for the resolved SessionFactory — session/transaction counts, entity and collection load/fetch/insert/update/delete counts, query execution counts, and query-cache/second-level-cache hit/miss/put counters (including per-region breakdowns) when those caches are enabled.
  • Supports manual refresh and optional auto-refresh, consistent with other Database-section runtime panels.
  • When collection is disabled, offers an explicit, policy-guarded runtime action that calls Statistics#setStatisticsEnabled(true). Collection begins at that moment and application configuration is not persisted or rewritten.

Availability:

  • Requires JPA/Hibernate ORM on the classpath and a resolvable SessionFactory (same manifest availability as the Hibernate Advisor panel).
  • Additionally requires hibernate.generate_statistics (or the Quarkus quarkus.hibernate-orm.statistics equivalent) to be enabled; when statistics are disabled, the API reports available: false with a human-readable unavailableReason and enableAvailable: true instead of an error. The UI offers runtime activation rather than fabricating data.

Out of scope for the current release surface:

  • Resetting or clearing Hibernate's Statistics counters from the UI or API.
  • Per-entity-class or per-query drill-down beyond what org.hibernate.stat.Statistics itself exposes.
  • Multi-persistence-unit applications: only the first resolved EntityManagerFactory/SessionFactory is inspected; this is a known limitation for a future iteration.
  • Filtering by bootui.monitoring.exclude-self: these counters are process-global on the SessionFactory, not attributable to an individual caller, so self-filtering does not apply here.

Acceptance criteria:

  • The panel never resets Hibernate's counters. Runtime activation happens only through explicit POST /bootui/api/hibernate-statistics/enable, is blocked by read-only policy, and does not persist configuration.
  • When no SessionFactory can be resolved, the panel reports a clear unavailable state and does not offer activation.
  • Spring MVC, Spring WebFlux, and Quarkus report equivalent statistics data through the same /bootui/api/hibernate-statistics contract wherever Hibernate ORM is present.

5.17.2 Flyway Panel

Purpose: answer "Which Flyway-managed databases exist, what schema version is applied, which migrations are applied or pending, and can I explicitly run safe local Flyway actions?"

Data sources:

  • Flyway beans discovered in the application context.
  • Each bean's Flyway.info().all() migration metadata (version, description, type, script, state, installed-by, installed-on, installed-rank, execution time, checksum).
  • Spring Modulith module identifiers and module-aware Flyway strategy presence, when available, to read the root and module-specific history tables that Spring Modulith derives from a registered Flyway bean.

Features:

  • List each Flyway bean as a database, with its current applied version plus applied and pending counts.
  • When Spring Modulith module-aware Flyway migrations are active, list the root and module-specific Flyway history tables as read-only entries instead of only the registered base Flyway bean.
  • For each migration, show version, description, type, script, state, installed-by, installed-on, execution time, and checksum.
  • Allow a confirmed migrate action unless the app or Flyway panel is read-only.
  • Allow a confirmed clean action unless the app or Flyway panel is read-only, and only when Flyway's own clean-disabled=false.

Out of scope for the current release surface:

  • Running repair, baseline, validate, rollback, or migration-file generation from the UI.

Acceptance criteria:

  • On Spring, absent Flyway classes prevent the typed provider and controller from registering. Quarkus keeps the neutral resource/service wired and reports Flyway unavailable when its capability is absent, without linking the optional Flyway provider.
  • When Flyway is present but no Flyway beans exist, the panel shows a clear empty state.
  • Opening the panel reads migration metadata through the configured Flyway integration (info().all() on Spring), which can query its schema-history table; it does not execute migrate, clean or another schema-changing action.
  • Mutating Flyway actions require browser confirmation and a non-read-only app and panel.
  • Mutating Flyway actions are blocked while Spring Modulith module-aware Flyway is active so BootUI does not bypass Spring Modulith's migration strategy or target the wrong module-specific history table.

5.17.3 Liquibase Panel

Purpose: answer "Which Liquibase-managed databases exist, which change sets have been applied or are pending, and can I explicitly apply pending change sets?"

Data sources:

  • SpringLiquibase beans discovered in the application context.
  • Each bean's configured Liquibase changelog and recorded change-log history (id, author, change-log, description, comments, execution type, date executed, order executed, checksum, tag, deployment id, contexts, labels).

Features:

  • List each SpringLiquibase bean as a database, with applied and pending change-set counts.
  • For each change set, show id, author, change-log, description, comments, execution type, date executed, order executed, checksum, tag, deployment id, contexts, and labels.
  • Allow a confirmed update action unless the app or Liquibase panel is read-only.

Out of scope for the current release surface:

  • Running rollback, dropAll, changelog generation, or any other Liquibase mutating command beyond confirmed update.

Acceptance criteria:

  • On Spring, absent Liquibase classes prevent the typed provider and controller from registering. Quarkus keeps the neutral resource/service wired and reports Liquibase unavailable when its capability is absent, without linking the optional Liquibase provider.
  • When Liquibase is present but no SpringLiquibase beans exist, the panel shows a clear empty state.
  • Opening the panel only reads changelog and history metadata; no Liquibase update command is executed as a side effect.
  • Mutating Liquibase actions require browser confirmation and a non-read-only app and panel.

5.17.4 Database Advisor Panel

Purpose: answer "Which deterministic structural risks exist in the application's physical database schema, and where does that schema disagree with explicit Hibernate mappings?"

Data sources:

  • Every discovered application DataSource, introspected through standard JDBC DatabaseMetaData for tables, columns, primary keys, foreign keys, and indexes.
  • PostgreSQL, MySQL, MariaDB, and Oracle (19c+) system catalogs for the bounded vendor-specific checks documented in docs/DATABASE-ADVISOR-CHECKS.md. Oracle catalog reads use only ALL_* dictionary views and SYS_CONTEXT('USERENV', ...), scoped to the connected session's CURRENT_SCHEMA, with no elevated privilege, no application-row query, no database link, and no production ojdbc dependency.
  • The Hibernate/JPA metamodel, when available, for cross-referencing explicitly named entity tables (including @SecondaryTable), columns, foreign keys, unique constraints, numeric precision/scale and IDENTITY key generation with observed schema metadata.

Features:

  • Run an explicit, read-only scan over a fixed generic ruleset covering missing primary keys, foreign-key columns without known supporting access paths, exact index-definition overlap, foreign-key/referenced-column domain discrepancies, redundant unique indexes, duplicate foreign key constraints, narrow auto-generated primary keys and MySQL/MariaDB/Oracle invisible or ignored indexes. These are contextual structural reviews, not workload or business-intent verdicts.
  • Augment the generic scan for PostgreSQL with invalid-index (excluding an index still building CONCURRENTLY), sequence-exhaustion, NOT VALID constraint, missing-replica-identity and unlogged-table checks; for MySQL/MariaDB with non-InnoDB-engine, non-utf8mb4, and AUTO_INCREMENT-exhaustion checks; and for Oracle with unusable-index, disabled/unvalidated-constraint, and sequence/identity-exhaustion checks. A driver-reported "Oracle" product name is confirmed against the server's version banner before Oracle augmentation runs, since some Oracle-compatible databases report the same product name; Tibero, OceanBase, EDB Postgres Advanced Server, and H2's Oracle compatibility mode are never classified as Oracle. Catalog-query failures do not fail the generic scan.
  • When a Hibernate metamodel is available, compare only entities with explicit @Table(name = ...) (and @SecondaryTable) declarations against observed metadata. Even explicit logical names may be transformed by a physical naming strategy; discrepancies do not establish broken effective runtime mappings. Composite foreign key matching tolerates the physical constraint's own column order but requires the same child-to-parent pairing, and an association explicitly declaring @ForeignKey(ConstraintMode.NO_CONSTRAINT) is excluded from the missing-constraint check.
  • Discover datasources through the same proxy-aware mechanism as Database Connection Pools and SQL Trace, resolving Spring's delegating and routing DataSource wrappers down to the physical pool behind them so the same datasource is never scanned twice. A wrapper is skipped only when the pool it forwards to is a bean of its own; when the wrapper owns the only reference to that pool — as spring.datasource.connection-fetch=lazy does with LazyConnectionDataSourceProxy — the wrapper itself is introspected, and a routing wrapper is expanded into its resolved targets named beanName[lookupKey]. A wrapper that cannot describe its target is reported as an unreadable datasource rather than dropped.
  • Report findings by severity and rule, with bounded sample evidence and remediation guidance, and cache the latest report until the next explicit scan.

Availability:

  • The generic rules run for every JDBC-reachable database vendor; databases without vendor-specific augmentation are not treated as unsupported.
  • Wrong-vendor checks (such as MySQL/Oracle checks in a PostgreSQL-only application) remain neutral SKIPPED/INFO diagnostics without incomplete qualification or completion credit. Missing catalog evidence for an applicable vendor, including unsupported server versions and mixed datasource coverage, remains incomplete.
  • Successful discovery of no application DataSource returns DISABLED; discovery failure, or a scan where no discovered schema could be read, returns ERROR. Individual failures retain other readable schemas with PARTIAL. Truncated or otherwise incomplete evidence cannot prove an object absent.
  • Hibernate cross-reference checks are skipped with an explicit reason when either the physical schema or Hibernate metamodel is unavailable; their absence does not prevent the generic JDBC rules from running.
  • Spring MVC, Spring WebFlux, and Quarkus use the same shared rule engine and report contract. Quarkus discovers datasources through CDI and reads configured names from Agroal's @DataSource qualifier reflectively, without an Agroal API dependency. Unqualified sources use positional fallback names (default, datasource-2, ...).

Out of scope for the current release surface:

  • Executing DDL, querying application data, changing schema objects, or changing Hibernate mappings.
  • Workload or query-plan analysis, partition management, and index suggestions derived from observed query usage.
  • Guessing effective physical names or sequence optimizer contracts from annotations alone.
  • Oracle orphaned-entry, chained-row, unused-index, fragmentation, and sequence-gap warnings, and any AWR/ASH-derived finding, none of which can be evaluated from ALL_* views alone without an elevated role or workload assumptions this advisor does not make.

Acceptance criteria:

  • Opening the panel returns only the cached report; schema introspection starts only after the developer explicitly invokes POST /bootui/api/database-advisor/scan.
  • The scan action is blocked by global read-only mode and bootui.panels.database-advisor.read-only.
  • A failure in PostgreSQL/MySQL/MariaDB/Oracle catalog augmentation does not discard generic JDBC findings, and an unreadable datasource does not discard findings from readable datasources.
  • The panel never executes DDL or queries application rows, and findings are presented as deterministic review prompts rather than automatic tuning instructions.
  • Equivalent inputs produce the same findings and report shape on Spring MVC, Spring WebFlux, and Quarkus, subject only to each adapter's native datasource names.

5.17.5 Transactions Panel

Purpose: answer "Which transaction boundaries recently ran, how did they complete, how were they nested, and which ones were slow or held multiple JDBC connections?"

Data sources:

  • Spring Framework's TransactionExecutionListener SPI, registered without replacing existing listeners against every discovered ConfigurableTransactionManager.
  • The bounded, framework-neutral TransactionRecorder.
  • SQL Trace's existing thread and time-window correlation for per-transaction SQL-statement and distinct-connection counts.
  • The active Micrometer/W3C trace context when a trace id is available.

Features:

  • Capture begin, commit, rollback, and failure outcomes for observed transaction boundaries, including the declared boundary name, best-effort NEW or PARTICIPATING propagation classification, active JDBC isolation, read-only flag, start/end timestamps, duration, thread, trace id, and error details.
  • Retain completed transactions most-recent-first in a configurable bounded ring buffer and render parent/child links as a nested transaction tree.
  • Report aggregate total, average, and maximum duration; commit, rollback, and unknown counts; nested-transaction count; and configurable slow-transaction and connection-hold threshold counts.
  • Correlate captured boundaries with SQL Trace without adding a second SQL instrumentation layer.
  • Provide explicit Pause/Resume and confirmation-gated Clear actions. Pausing preserves retained entries and does not deregister the listener.
  • Stream coalesced change notifications over /bootui/api/transactions/stream when a transaction completes, recording changes state, or the buffer is cleared. The browser closes the stream when auto-refresh is disabled or the tab is hidden and retains manual refresh as a fallback.

Availability:

  • Spring MVC and Spring WebFlux capture boundaries from configurable transaction managers, blocking and reactive. BootUI contributes its listener through Spring Boot's transaction-manager customization and completes registration for user-defined managers after singleton initialization. The listener pairs each boundary's callbacks by the transaction execution Spring passes them, so a reactive transaction that completes on another thread than it began is recorded once, without a thread-bound parent or isolation.
  • The panel returns a clear unavailable report when transaction capture is disabled, no ConfigurableTransactionManager is present, or capture is otherwise not configured.
  • Transactions are not applicable on Quarkus. Narayana JTA and the CDI @Transactional interceptor expose no comparable per-boundary listener without invasive interception, so the Quarkus adapter reports the panel unavailable rather than providing lower-fidelity capture.

Out of scope for the current release surface:

  • Adding an invasive Quarkus transaction interceptor.
  • Changing transaction propagation, isolation, rollback rules, or application transaction-manager configuration.
  • Retaining an unbounded transaction history or recording application payloads and SQL parameter values.

Acceptance criteria:

  • Listener registration composes with the application's transaction management and listeners; BootUI never wraps or replaces a transaction manager.
  • Retention is bounded, nested boundaries preserve parent/child relationships while retained, and evicted parents do not hide retained child entries.
  • POST /bootui/api/transactions/recording and POST /bootui/api/transactions/clear are blocked by global read-only mode and bootui.panels.transactions.read-only; clearing additionally requires explicit browser confirmation.
  • Spring MVC and Spring WebFlux expose the same report and action contract, with equivalent SSE change semantics.
  • R2DBC-only WebFlux applications can capture boundaries from a configurable reactive transaction manager, but SQL and connection counts remain JDBC-only and do not prove that no database work occurred. Without a configurable manager the panel is unavailable. Quarkus reports the panel not applicable and does not advertise get_transactions.

5.17.6 SQL Trace Rankings and Request Attribution

Purpose: answer "Which statements dominate the database time I just captured, and which inbound request routes are paying for them?"

Data sources:

  • The existing bounded SqlTraceRecorder window. No second statement recorder, JDBC wrapper, or SQL parsing library is introduced.
  • The adapter's existing inbound-request evidence: the Spring MVC request-correlation registry, the Spring WebFlux HTTP exchange trace registry, and the framework-neutral HTTP exchange buffer on Quarkus.
  • The adapter's existing trace-id provider, plus the framework's own best-matching route pattern where the adapter already resolves one.
  • The Mappings panel's existing MappingProvider, used to resolve a captured path back to a declared route template on a runtime that reports no per-request pattern. No new route enumeration is introduced.

Features:

  • Aggregate retained executions by a normalized statement. Normalization is a dependency-free lexical pass that replaces string, numeric, and boolean literals and existing bind markers with ?, collapses IN (...) lists, and normalizes whitespace, so equivalent parameterized executions fold into one group without exposing any bound value.
  • Rank normalized statements by cumulative duration, maximum duration, execution count, average duration, error count, p95 duration, and p99 duration. Each ranked group carries all metrics, p50/p95/p99 durations over the retained window, its share of retained database time, the criteria it is actually top-ranked for (topFor), the existing N+1 flag and call sites, and bounded execution ids for deep linking with an explicit entryIdsTruncated flag when the group ran more times than the deep link retains. A group that scores zero on a criterion is never ranked for it.
  • Attribute executions to inbound requests using trace id first, then the serving thread only where thread affinity is reliable, then the request's time window. Each tier requires a unique candidate; two or more equally good candidates produce an ambiguous result rather than a guess.
  • Group attributed work by the framework's normalized route template when the adapter supplies one; otherwise resolve the captured path against the application's declared route templates, and fall back to a masked request path (identifier-looking segments replaced with {value}, query string and fragment discarded) only when neither is available. Template resolution is conservative: a declared template must match segment for segment and be the single most literal match, and two equally plausible declarations resolve to no template at all. Raw query strings and path-parameter values are never used as a grouping key, and each route group declares its routeSource.
  • Report per-route requests, executions, distinct statements, cumulative/maximum/average duration, error count, share of retained database time, the correlation tiers that produced the grouping, and a bounded list of that route's top statements.
  • Keep executions that could not be correlated and executions that more than one request matched in explicit unattributed and ambiguous buckets, each with its own executions, duration, error count, share, and a reason.
  • State the retained window (retained statements, buffer size, evictions, total captured, oldest and newest retained timestamps, total retained duration) in the contract so rankings are read as bounded diagnostic evidence rather than lifetime metrics.
  • Record each execution's duration in microseconds and sum every ranking, percentile, share and attribution figure from that, reporting them in fractional milliseconds. A local database answers most statements in well under a millisecond, so a whole-millisecond recording unit would make the rankings report zero for almost every statement. The slow-query threshold stays configured in milliseconds and its meaning is unchanged.
  • Serve the whole report from a single safe GET /bootui/api/sql-trace/insights, computed on request. Nothing is computed on page load beyond the panel's normal fetch, and no state is mutated.

Availability:

  • Available on Spring MVC, Spring WebFlux, and Quarkus whenever SQL Trace itself is available. The report declares which correlation tiers the runtime supports instead of silently degrading.
  • Spring MVC supports trace-id, serving-thread, and time-window correlation, and supplies route templates from the best-matching handler pattern.
  • Spring WebFlux supports trace-id and time-window correlation only, because a reactive request is not pinned to one thread; it supplies route templates from the best-matching reactive pattern. Its request evidence ships with the OpenTelemetry correlation configuration, so without an OpenTelemetry starter route attribution reports itself unavailable with that reason instead of showing every statement as unattributed. Statement rankings are unaffected.
  • Quarkus supports trace-id and time-window correlation only. RESTEasy Reactive exposes no per-request route template to the adapter, so Quarkus resolves route templates from the application's declared JAX-RS @Path mappings and falls back to a masked path when no declaration matches unambiguously.

Out of scope for the current release surface:

  • Execution plans, table statistics, index recommendations, and active query profiling.
  • Capturing bind values, query strings, path-parameter values, request bodies, or any SQL text beyond what the SQL Trace recorder already retains.
  • Persistence beyond the SQL Trace retention window, lifetime counters, SQL rewriting, and any causal claim that the retained evidence does not support.

Acceptance criteria:

  • Ranking, normalization, aggregation, bounding, attribution, and advisory policy live in framework-neutral, JSON-free engine services; core DTO records stay immutable, annotation-free, and byte-compatible across Jackson 3 and Jackson 2.
  • Equivalent parameterized executions aggregate into one normalized group and no ranked statement, route key, or advisor finding exposes a bound value, raw query string, or path-parameter value.
  • Ranked statements, routes, and the route-by-statement cross product are bounded before serialization, and truncation is disclosed in the payload rather than silently applied.
  • A statement whose owning request cannot be uniquely identified appears in the unattributed or ambiguous bucket and never in a route group. A statement carrying a trace id that no retained request carries is never attributed by a weaker tier, and a statement that was already running before a request began is never absorbed into it.
  • GET /bootui/api/sql-trace/insights is a safe read that honors localhost, Host, and panel enablement policy identically on Spring MVC, Spring WebFlux, and Quarkus, and returns the same report shape on all three.
  • The Database advisor's DB-RUNTIME-001 rule reports only statement shapes with repeated distinct texts and a literal in a filtering position, states its confidence and limitations, and includes no captured literal values.

5.17.7 PostgreSQL Panel

Purpose: answer "What do PostgreSQL's own statistics and catalog views report about this database right now?", as a runtime view rather than an assessment. The panel shows the server's own numbers and grades none of them: it carries no rule catalogue, no findings, no severities and no score.

Data sources:

  • Discovered application JDBC DataSource beans, using the same datasource discovery seam as the Database advisor.
  • PostgreSQL pg_stat_*, pg_catalog, pg_settings, pg_stat_statements when installed, and replication/catalog views.

Features:

  • Return an initial NOT_READ report until the developer explicitly invokes POST /bootui/api/postgresql/read.
  • Run a bounded read-only transaction with pinned statement_timeout, lock_timeout, and idle-in-transaction timeout.
  • Report per-section AVAILABLE, SKIPPED, or FAILED status for vital signs, sessions, statements, indexes, tables, vacuum, replication/WAL, and settings. Skipped or failed sections are reported with their reason and remain limitations, never empty tables.
  • Scope the session snapshot and the session breakdown to current_database(), and take the connection total from pg_stat_database so it remains correct for a role that cannot see other backends.
  • Return, and render as tables, the rows each section read: the live pg_stat_activity session snapshot with state, wait event, blocking pids, transaction age and statement; the top normalized statements; index usage; relation size and access shape; autovacuum state; replication and WAL; and the curated settings.
  • Carry a report-level list of what the read does not cover, assembled from every degraded section, truncation and unread datasource.
  • When only the top-N statement ranking is truncated, render its omission once as a section-level informational note rather than page-wide warnings or warning badges. Other row caps and actual read failures remain prominent. This presentation does not change the API's PARTIAL, truncated, or limitations fields.
  • Reserve truncated for row caps. Budget exhaustion carries a section reason, retaining any rows already read; no retained rows means a failed section, not an empty successful one. Incomplete relation lists do not replace the comparison baseline.
  • Configure per-datasource row limits through the same bootui.postgresql.* properties on all adapters. Defaults: 100 sessions, 100 statements, 500 indexes, 200 tables, 200 autovacuum entries, 10 replicas, and 40 curated settings. Limits require restart, reject non-positive values and integers that cannot accommodate the extra truncation-detection row, and do not change the existing timeout or statement-text bounds. See docs/PROPERTIES.md for the keys.
  • Exclude the timeout settings BootUI overrides for its own read from the notable application settings.
  • Expose replication.replicasAvailable so an empty replica list is only evidence of absence when it was read. On a standby the replica list and primary-relative lag are not read; cascading replicas may still be connected.
  • Keep only the previous read in memory to show simple deltas; no baseline is written to disk.

Availability:

  • Spring MVC, Spring WebFlux, and Quarkus expose the same endpoint and report contract, and offer the panel only when a PostgreSQL datasource is configured. Availability is decided from declared configuration alone — a datasource's JDBC URL, including through a wrapping driver, or the Quarkus db-kind — and never opens a connection. A datasource that declares no readable URL cannot be ruled out and keeps the panel available; a non-PostgreSQL datasource reached by the read is skipped with diagnostics rather than treated as a failure.
  • A read-only database role with pg_monitor membership is recommended for complete statistics. Without it pg_stat_activity removes the rows of backends the role does not own while pg_stat_statements keeps its rows and replaces the statement text with <insufficient privilege>; neither is detectable from the result set, so the read probes pg_read_all_stats membership and degrades both sections rather than reporting a short, confident list. The statement ranking is degraded on the placeholder appearing as well as on the probe. pg_stat_replication restricts a third way: every replica is still listed, but its state, sync state and lag are hidden, which degrades that section.
  • The statements section is SKIPPED unless pg_stat_statements is installed.
  • Connections borrowed with auto-commit disabled are refused before metadata or SQL, without commit or rollback. This includes pool defaults such as spring.datasource.hikari.auto-commit=false; the read reports an explicit error rather than risking an application-owned transaction.

Out of scope for the current release surface:

  • Monitoring, alerting, baselining to disk, query-plan capture, DDL, cancelling sessions, changing settings, or reading application table rows.
  • Attributing PostgreSQL counters to this JVM only; the counters are cumulative since the last reset and cover every client of the database.

Acceptance criteria:

  • Opening the panel never contacts PostgreSQL; only the explicit read action does.
  • The read action is blocked by global read-only mode and bootui.panels.postgresql.read-only, despite being read-only at the database.
  • Row caps, timeouts, and section failures produce partial/diagnostic reports instead of silent clean reports.
  • The panel reports a runtime observation of one database, not a repeatable assessment of the application, so it carries no findings and no score, and never contributes to the Scorecard's advisor scoring or retained-findings totals.
  • A session, statement, index, relation, autovacuum or settings row that the read retained is rendered in its section's table; a section that could not be read shows its reason and hint instead of an empty table.
  • Autovacuum "due" is computed per relation from the cluster autovacuum settings overridden by that table's own reloptions (including PostgreSQL 18's autovacuum_vacuum_max_threshold), and the remaining approximations — the live-tuple estimate, and the unmodelled PostgreSQL 13+ insert-triggered trigger — are stated on the section.
  • A borrowed connection that could not be restored to the state it was found in degrades the read, exactly like a session whose bounds could not be pinned, rather than only appearing in the diagnostics.

5.17.8 MySQL Panel

MySQL is an operational sibling to PostgreSQL, not another Database advisor. Oracle MySQL 8.4 LTS and 9.7 LTS are the tested server lines, with live coverage on 8.4.6 and 9.7.2 using Spring's Connector/J 9.7.0 / HikariCP 7.0.2 and Quarkus' Connector/J 9.6.0 / Agroal 3.0.1. Any other Oracle MySQL Community or Enterprise version is read with an INFO diagnostic naming it as untested; sections the server cannot answer report their own reasons, and the read-only session guards still fail closed. Other compatible/managed flavors are skipped with an INFO diagnostic. MariaDB reached through MySQL Connector/J (detected from the driver's product version before any SQL, and confirmed by @@version) is read on a best-effort basis and labelled unsupported: serverFlavor is MARIADB, an INFO diagnostic names the gaps, and the UI shows an Unsupported badge. It uses MariaDB's max_statement_time session and per-statement guards, information_schema InnoDB lock views for row-lock waits, and a settings list without super_read_only and information_schema_stats_expiry. It reports no replication receiver state, which stays unknown, and computes no counter comparisons, because there is no @@server_uuid. MariaDB 11.4 LTS and 11.8 LTS were checked manually only. MariaDB Connector/J (jdbc:mariadb:, Quarkus db-kind=mariadb) is not a supported declaration.

  • Use existing default/named JDBC datasources on Spring MVC, Spring WebFlux, and Quarkus. R2DBC-only and reactive-client-only applications are unavailable, not partially supported JDBC applications. Discovery uses local declarations without borrowing a connection; the action verifies the connected vendor/server.
  • Cover vital signs, sessions with bounded blocking relationships, normalized statement ranking, index activity, table estimates, InnoDB, basic local replication-channel state, and allow-listed settings. There are no grades, advisor rules/recommendations, severity counts, or contributions to Scorecard scores.
  • GET /bootui/api/mysql returns the latest sanitized in-memory report, initially NOT_READ. Only explicit POST /bootui/api/mysql/read starts collection; there are no SQL/schema/endpoint arguments or automatic refresh. MCP get_mysql_report / mysql_read and CLI bootui db mysql report / bootui db mysql read share that cache.
  • Enforce one single-flight read across transports. Apply existing canonical policy/busy responses and global/panel read-only controls, even though collection does not mutate application data. Quarkus remains production-dark.
  • Distinguish report states NOT_READ, READ, PARTIAL, ERROR, DISABLED and per-section AVAILABLE, SKIPPED, FAILED with reasons. READ asserts completion within declared scope, never health. Partial sub-reads preserve independent usable rows; no usable evidence from supported targets means ERROR. No supported datasource means DISABLED. An empty successful channel read is evidence; failed replication reads are unknown.
  • Identify SERVER, SELECTED_SCHEMA, and DEFAULT_SCHEMA_ASSOCIATED evidence. Missing schema skips schema-specific work rather than enumerating databases. Default-schema association is not exhaustive cross-schema attribution; repeated server counters from multiple pools must not be summed.
  • Return unknown values as null, distinguish observed zero, and preserve large/unsigned counters, byte sizes, and numeric identifiers as exact decimal strings in JSON. Bounded row counts and typed duration fields remain numbers; generic metric value fields are nullable strings with explicit unit, scope, and source. Converted timers name their millisecond/second units. Both serializers and the UI must preserve precision.
  • Declare readability, collection enabled/disabled/unknown, and timing separately. Permissions, active roles, instrumentation filters, server digest overflow, cached estimates, and BootUI top-N bounds qualify evidence. Never treat missing timing as zero latency or handler operations as query/physical-I/O counts. Global digest collection requires global_instrumentation and statements_digest, not thread_instrumentation.
  • Keep comparisons in memory, with each baseline's own interval and compatible datasource/server/scope/provenance. Restart/reset or server changes invalidate affected comparisons; unavailable counters do not produce rates.
  • Use the seven static per-datasource bootui.mysql.* caps documented in Properties. Positive values below 2147483647 are required at startup. An extra-row check establishes truncation, not a full cap alone. Bound nested lock/worker detail and preserve logical composite-index integrity.
  • Reserve truncated for row omissions. Timeouts and failed/disabled sources have their own section reasons. Current bounds are a 15-second cooperative whole-read budget, 5-second server SELECT limit, 2-second metadata-lock wait limit, and 400 displayed statement characters. The JDBC network guard is at most 7 seconds, preserving a tighter existing positive timeout. Application-controlled pool acquisition can exceed the cooperative budget; connection/socket configuration is separate. These are not hard end-to-end deadlines.
  • Use server MAX_EXECUTION_TIME hints capped by the remaining budget for SELECTs (MariaDB: SET STATEMENT max_statement_time=... FOR, in an executable comment Connector/J accepts). If performance_schema.global_status is unreadable, use SHOW GLOBAL STATUS filtered to 17 fixed safe names: the allow-list bounds output, and the JDBC network guard covers I/O. Do not claim a SELECT execution-time guarantee for SHOW or control statements. Restore the original network timeout afterward.
  • Use neither Statement.setQueryTimeout nor Statement.cancel, avoiding Connector/J's auxiliary KILL QUERY connections regardless of enableQueryTimeouts. Establish a BootUI-owned transaction through actual SET SESSION transaction_read_only=1 and START TRANSACTION READ ONLY, and verify the server setting. Do not use the routing-sensitive Connection.setReadOnly hint.
  • Refuse manual-commit connections before metadata/SQL without committing or rolling them back. Roll back only BootUI's transaction and restore original session/network/auto-commit state before returning a handle. On cleanup failure, abort before closing; if abort fails, quarantine the handle and disable collection until the pool and BootUI restart. Do not force fresh Information Schema statistics.
  • Never select LOCK_DATA, QUERY_SAMPLE_TEXT, PROCESSLIST_INFO, TRX_QUERY, application rows, arbitrary variables, or replication secrets. Only normalized digests pass through masking, redaction, exposure, and text bounds. Invalidate the sanitized cache on exposure-policy changes without SQL, including changes during collection.

Out of scope: raw/sample SQL, a SQL console, query-plan execution, session-kill controls, maintenance/DDL, instrumentation enable/reset, persistent monitoring, precise end-to-end replication lag, and topology administration. Live restricted-account and MySQL-available REST/MCP/CLI contracts cover all three adapters, including custom mounts and pooled connection cleanup. See MySQL and the roadmap.

5.18 Cache Panel

Purpose: answer "Which cache managers and caches exist, how are they used, and can I clear them during local development?"

Data sources:

  • Spring CacheManager beans discovered in the application context.
  • CacheOperationSource metadata for @Cacheable, @CachePut, @CacheEvict, and composed @Caching operations.
  • Micrometer cache meters when the host application has cache metrics registered.

Features:

  • List detected cache managers, their implementation types, and currently known cache names.
  • For each cache, show the native implementation type, safe local size when it can be read without remote enumeration, and Micrometer metrics such as hits, misses, hit ratio, puts, evictions, removals, and size.
  • List discovered cache annotation operations by bean, target type, method signature, operation type, cache names, key/condition/unless expressions, and eviction flags.
  • Clear one known cache or every known cache when bootui.cache.clear-enabled=true and the browser sends an explicit confirmation.

Acceptance criteria:

  • When no CacheManager beans are present, the panel shows a clear empty state.
  • Cache size inspection must avoid enumerating remote or distributed cache stores.
  • Cache clear actions are enabled by default for local development, still require explicit confirmation, and return a clear disabled response when bootui.cache.clear-enabled=false.
  • Clearing unknown cache names must not create dynamic caches as a side effect.
  • Annotation discovery must not eagerly initialize lazy application beans.
  • BootUI's own cache managers, cache operations, and cache metrics are hidden by default.

5.19 Dev Services Panel

Purpose: answer "Which local backing services are connected?"

Data sources:

  • Spring Boot service connection metadata when available.
  • Spring Boot Docker Compose startup service snapshot when available.
  • Testcontainers beans that are present in the application context.
  • Quarkus Dev Services started for the application (Quarkus).

Features:

  • Show detected service connections:
    • PostgreSQL.
    • MySQL.
    • MariaDB.
    • Redis.
    • MongoDB.
    • RabbitMQ.
    • Kafka.
    • Elasticsearch.
    • Neo4j.
    • Zipkin.
  • Show source:
    • Docker Compose.
    • Testcontainers.
    • connection details.
  • Show sanitized connection details.
  • Show bounded logs when a bean-backed Testcontainers service exposes them, masking secret-like assignments and authorization credentials under MASKED before the tail is cut, omitting them under METADATA_ONLY with logsOmitted set, and returning them verbatim only under FULL or with bootui.mask-secrets=false.
  • Show a restart action for bean-backed services only when explicitly enabled with bootui.dev-services.restart-enabled=true.
  • Skip lazy, prototype, abstract, or otherwise uninitialized service beans instead of creating them from a read-only panel request, and show a warning that explains why they were skipped.

Status: implemented and supported for the 1.0 release surface.

Acceptance criteria:

  • Secrets are never displayed.
  • bootui.expose-values=FULL is the only mode that may reveal secret-like service connection values, and should be used only in trusted local sessions.
  • Unknown services are represented generically.
  • Works even when Docker is not installed.
  • Docker Compose services are clearly identified as startup snapshots because Spring Boot does not expose live per-service lifecycle state.
  • Docker Compose snapshots preserve duplicate or unnamed service entries by assigning stable synthetic IDs and warning when BootUI had to adjust an entry for display.
  • Restart controls are disabled by default and warn that already-created client beans may not reconnect after container ports change.

5.20 Threads Panel

Purpose: answer "What are the application's threads doing right now?"

Data sources:

  • In-process java.lang.management.ThreadMXBean snapshot (dumpAllThreads, findDeadlockedThreads / findMonitorDeadlockedThreads, and CPU/user time where supported), read directly rather than requiring the host app to expose the Actuator threaddump endpoint over HTTP.

Features:

  • Show a single bounded thread snapshot per request with a per-state count summary.
  • Detect and flag deadlocked threads.
  • Report virtual-thread context when running on a JDK that supports it.
  • Filter threads by name and by state, with stable server-side paging.
  • Expand a thread to view its stack trace.
  • Offer a confirmation-gated raw text thread dump download as a mutating POST that is blocked when the panel is read-only.

Status: implemented and supported for the 1.0 release surface.

Acceptance criteria:

  • Thread names and stack frames are routed through the existing masking and value-exposure model; stack traces are omitted under metadata-only exposure and secret-like names are masked unless bootui.expose-values=FULL.
  • The panel fails closed, returning a stable empty report with an explained unavailable reason when thread information cannot be read.
  • The raw dump download is a POST and is blocked when the panel is read-only.
  • The UI does not issue the download POST until the developer explicitly confirms the capture.

6. Technical architecture

6.1 Current repository layout

BootUI/
├── pom.xml
├── bootui-engine/
├── bootui-conformance/
├── bootui-ui/
├── bootui-cli/
├── bootui-agent-bridge/
├── bootui-agent/
├── bootui-coverage/
├── bootui-spring-boot-starter/
├── bootui-spring-sample-app/
├── bootui-spring-webflux-sample-app/
├── bootui-quarkus-parent/
├── bootui-quarkus/
├── bootui-quarkus-deployment/
├── bootui-quarkus-integration-tests/
└── bootui-quarkus-sample-app/

6.2 Modules

Shared modules:

  • bootui-engine: the immutable DTO records, secret masking, version metadata, and safe value rendering of the io.github.jdubois.bootui.core package, the framework-neutral services and advisor engines built on them, and the neutral io.github.jdubois.bootui.spi ports. The bytecode-reading advisors here are Kotlin-aware — compiler-generated members and classes are filtered out and suspending functions are judged on their declared signature — implemented by bytecode name only, so no Kotlin runtime dependency is added and every adapter behaves identically.
  • bootui-conformance: the shared HTTP contract suite and golden panel manifests run against every adapter.
  • bootui-ui: the Vue 3 / Composition API / Vite / Bootstrap 5.3 SPA, built once into META-INF/resources/bootui/ and served unchanged by every adapter.
  • bootui-cli: the bootui command-line interface, a picocli tree generated from the engine's tool catalog and published as a runnable uber-jar (the all classifier), together with the dependency-free client for the command-line endpoint, the io.github.jdubois.bootui.client package — URL and token handling, tool invocation, outcome mapping, and an opaque JSON tree. The client reaches nothing outside the JDK, not even BootUI's DTOs or picocli, so it stays version-compatible with applications it was not built against; picocli is an optional dependency of bootui-cli, so tooling that depends on it for the client gets no dependency at all.
  • bootui-agent-bridge: the JDK-only bootstrap contract, shaded into bootui-agent and never published separately. The engine locates it through bootstrap-loader method handles, never a production dependency.
  • bootui-agent: the optional development-time -javaagent jar; it shades its bridge and Byte Buddy, and remains dormant until an active BootUI application claims it.
  • bootui-coverage: the non-published coverage aggregate, built only with the coverage profile.

Spring Boot modules:

  • bootui-spring-boot-starter: the drop-in Spring MVC and Spring WebFlux starter: shared auto-configuration, thin endpoint bindings, Spring SPI implementations, safety filters, Spring bootstrap integrations, and the bundled UI. It brings no web stack, so the application's own spring-boot-starter-web or spring-boot-starter-webflux decides which binding activates.
  • bootui-spring-sample-app: Spring MVC reference app and Playwright end-to-end suite.
  • bootui-spring-webflux-sample-app: Spring WebFlux reference app and conformance target.

Quarkus modules:

  • bootui-quarkus-parent: shared Quarkus LTS BOM and plugin management.
  • bootui-quarkus: runtime JAX-RS/Vert.x resources, Quarkus SPI implementations, producers, and safety filters.
  • bootui-quarkus-deployment: build-time wiring, capability gates, bean registration, and production-dark activation.
  • bootui-quarkus-integration-tests: Docker-free @QuarkusTest conformance and smoke tests.
  • bootui-quarkus-sample-app: Quarkus reference app.

Dependency direction is one-way, by package inside bootui-engine and by module beyond it: io.github.jdubois.bootui.core depends only on the JDK, the engine and SPI packages depend on it, and each framework adapter depends on bootui-engine. CoreBoundaryArchitectureTests keeps the core package off the engine, the SPI, the adapters, and every external library. bootui-cli sits outside that chain entirely: its client package depends on nothing, its command line on picocli (optional), and it uses bootui-engine only in test scope, to generate its command manifest. Maven Central receives seven coordinates, each with a flattened, parentless POM: bootui-engine, bootui-ui, bootui-spring-boot-starter, bootui-quarkus, bootui-quarkus-deployment, bootui-cli, and bootui-agent. The shared engine (with its core package), conformance, and UI modules never depend on Spring or Quarkus. JSON parsing and serialization stay in the adapters because Spring Boot and Quarkus use incompatible Jackson major versions.

Core DTO immutability is enforced, not just documented. Every collection component of a core DTO record is defensively copied in the record's compact constructor, so a caller cannot change a published report by mutating the collection it passed in or the collection an accessor returned, and a null collection is normalized to an empty one. The copies preserve the caller's iteration order — in particular map components are copied into a LinkedHashMap rather than through Map.copyOf, whose hash order is randomized per JVM run — so the serialized JSON stays byte-identical across Jackson 3 and Jackson 2.

The Maven build installs the configured Node.js and npm versions, builds the frontend before Java resources are packaged, and produces adapter artifacts that already contain the compiled UI. Consumers only add the matching Spring starter or Quarkus extension; they do not run a frontend build.

6.3 Runtime architecture

flowchart TD
    S[Spring Boot MVC or WebFlux app] --> SA[Spring adapter]
    Q[Quarkus dev app] --> QA[Quarkus runtime and deployment adapter]
    SA --> E[Framework-neutral engine and SPI]
    QA --> E
    E --> C[Core DTOs and masking]
    SA --> API[Stable BootUI REST API]
    QA --> API
    UI[Shared Vue UI] --> API

6.4 API design

BootUI should expose its own development-only API under:

/bootui/api/**

The browser UI should not depend directly on raw Actuator response shapes. BootUI should normalize them into stable DTOs. High-cardinality list endpoints should support bounded server-side q / filter / offset / limit access and return page metadata so the SPA can avoid fetching every row before filtering.

Advisor violation pages

The seven compact rule advisors (architecture, hibernate, spring, rest-api, memory, security, database-advisor) share GET <api>/<advisor>/rules/{ruleId}/violations on their supported MVC, WebFlux, and Quarkus stacks. The spring root remains the Quarkus application advisor on Quarkus; this does not change availability.

Reports retain their existing violationCount and sampleViolations previews (normally ten per rule, twenty for the Quarkus application and Security advisors), plus violationDetails: {scanId, total, retained, retentionLimit, truncated, locationNotes}. The snapshot ID is null before a completed scan. Totals and retention are before dismissal and describe retrieval completeness, not evidence coverage or score eligibility. The latest completed report and sanitized detail index are published together; reads during another scan serve the previous snapshot. Dismiss/restore preserves its identity and retained entries. Only the latest snapshot is kept.

Detail reads require the report's nonblank scanId; offset defaults to zero and limit to 100, capped at 1000. Malformed/fractional/overflowing inputs, negative offsets, and nonpositive limits are rejected. Responses contain scanId, ruleId, full violationCount, retainedCount, truncated, violations, page: {total, matched, offset, limit, returned, hasMore}, and locations. Page totals count retained entries; a terminal page does not prove complete retention. Offsets at/beyond the retained end return an empty terminal page. Ids outside the advisor's rule catalogue (Unknown advisor rule: ...) and catalogue rules without findings, whether passed, skipped, or failed (Advisor rule has no findings in the current scan.), both return 404 with those distinct messages; missing or stale snapshots return 409 with cached-report refresh guidance. Dismissed findings are retrievable. Reads obey panel availability, enabled and safety policy, but are allowed in read-only mode, and never rescan or collect new observations.

Architecture, REST API, and Hibernate results add sampleLocations, aligned index-for-index with sampleViolations, and their pages fill locations, aligned with violations. Each non-null entry is an AdvisorViolationLocationDto {className, memberName, kind, sourceFile, line, sourcePath, precision} for the one code element the finding names; an empty list means no listed violation has one, which is always the case for the other advisors. Locations are captured from scan evidence and completed with a local source path only during an explicit scan; detail reads never resolve them again. See violation locations.

The UI offers View violations only on demand when more findings exist, then bounded inline Previous/Next and Back to samples. It retains samples/the last page through loading or failure, provides Retry or an explicit Refresh cached report after 409 (never rescan), rejects stale responses, and displays accurate retained ranges and truncation warnings. Legacy reports without metadata keep their samples without an unusable detail control. No detail request occurs on mount, report arrival, or dismissal. Controls have unique accessible names and targets, keyboard focus handling, and one async announcement per rule.

The same contract is exposed by the seven get_*_rule_violations MCP tools with required id and scanId, and optional offset/limit; CLI paths are <advisor> violations <ruleId> --scan-id ... --offset ... --limit ..., using db for Database Advisor. Transport result/response-byte budgets still apply. See advisor retrieval for exact names and REST/MCP/CLI examples. GraalVM/CRaC, Pentesting, and Vulnerabilities retain their separate models.

Initial endpoints:

EndpointMethodPurpose
/bootui/api/overviewGETApp, runtime, framework name/version, profiles, BootUI status, and the current run
/bootui/api/panelsGETPanel availability, enabled state, and read-only state
/bootui/api/githubGETLocal GitHub origin metadata and the latest cached dashboard snapshot
/bootui/api/github/refreshPOSTExplicit bounded GitHub API refresh for project metrics and quotas
/bootui/api/beansGETSearchable bean summary
/bootui/api/conditionsGETAuto-configuration conditions
/bootui/api/configGETEffective configuration values
/bootui/api/config/overridesPOSTCreate or update a local runtime configuration property override
/bootui/api/config/overrides/{name}DELETERemove a local runtime configuration property override
/bootui/api/mappingsGETHTTP mappings
/bootui/api/mappings/flatGETStable, paged HTTP mapping summaries
/bootui/api/healthGETHealth tree
/bootui/api/http-sessionsGETBounded local embedded-Tomcat HTTP session report
/bootui/api/http-sessions/{key}/clearPOSTClear attributes from a selected HTTP session after explicit confirmation
/bootui/api/http-sessions/{key}/invalidatePOSTInvalidate a selected HTTP session after explicit confirmation
/bootui/api/loggersGETLogger levels
/bootui/api/loggers/{name}POSTChange logger level
/bootui/api/startupGETStartup timeline
/bootui/api/threadsGETStable, paged live thread snapshot with state counts and deadlock info
/bootui/api/threads/downloadPOSTConfirmation-gated raw text thread dump download
/bootui/api/metricsGETMicrometer meter list with search, type, group, provenance, and explanation filters, paged at 200 by default (1,000 maximum)
/bootui/api/metrics/detailGETMeter detail with tag filters and samples paged at 100 by default (1,000 maximum)
/bootui/api/database-connection-pools/poolsGETJDBC connection pool metadata
/bootui/api/database-connection-pools/pools/{name}/snapshotGETLive connection pool utilization snapshot
/bootui/api/vulnerabilitiesGETRuntime Maven dependency inventory without external scanning
/bootui/api/vulnerabilities/scanPOSTExplicit on-demand OSV.dev vulnerability scan
/bootui/api/devtoolsGETSpring Boot DevTools status
/bootui/api/devtools/livereloadPOSTTrigger a DevTools LiveReload notification when available
/bootui/api/devtools/restartPOSTSchedule a DevTools restart after explicit confirmation
/bootui/api/live-memoryGETJVM memory report
/bootui/api/live-memory/offloadPOSTClear BootUI's in-memory capture buffers and request a GC; blocked in read-only mode
/bootui/api/live-memory/footprintGETRead process-local low-footprint state without changing capture
/bootui/api/live-memory/footprintPOSTEnable or disable the recording hold; blocked in read-only mode
/bootui/api/jvm-tuningGETJVM tuning advisor report
/bootui/api/heap-dumpGETHeap dump capture inventory and latest value-free histogram report
/bootui/api/heap-dump/capturePOSTCapture a local heap dump after explicit confirmation
/bootui/api/heap-dump/analyzePOSTAnalyze the latest heap dump class histogram
/bootui/api/heap-dump/deletePOSTDelete a retained heap dump
/bootui/api/heap-dump/downloadGETDownload a raw heap dump only when explicitly enabled
/bootui/api/scheduledGETScheduled tasks
/bootui/api/fault-toleranceGETFault tolerance policy inventory and bounded fault tolerance events
/bootui/api/http-probePOSTLocal HTTP probe
/bootui/api/log-tail/recentGETRecent log lines
/bootui/api/log-tail/streamGETLog stream over Server-Sent Events
/bootui/api/exceptionsGETBounded exception groups with status and occurrence summaries
/bootui/api/exceptions/caughtGETCaught-in-application-code outcomes from the opt-in caught-exceptions sensor and runtime journal; unavailable without their evidence, never proof of swallowed errors from silence
/bootui/api/exceptions/{id}GETOne exception group's detail: stack frames, causes, and retained occurrences
/bootui/api/exceptions/{id}/statusPOSTSet an exception group's triage status ({"status": ...})
/bootui/api/exceptionsDELETEClear retained exception groups when not read-only
/bootui/api/exceptions/streamGETExceptions change notifications over Server-Sent Events (re-fetch trigger)
/bootui/api/http-exchangesGETRecent application HTTP request/response metadata
/bootui/api/http-exchanges/routesGETRoute performance rankings over the retained HTTP exchanges
/bootui/api/tracesGETRecent local trace summaries
/bootui/api/traces/{traceId}GETTrace waterfall detail
/bootui/api/tracesDELETEClear retained local traces when not read-only
/bootui/api/otlp/v1/tracesPOSTEmbedded local OTLP/HTTP trace receiver
/bootui/api/ai/overviewGETAI telemetry summary from local spans
/bootui/api/ai/chatsGETRecent AI chat span groups
/bootui/api/ai/chats/{spanId}GETAI chat span detail
/bootui/api/ai/tokensGETAI token usage time series
/bootui/api/profile-diffGETProfile-specific property sources
/bootui/api/dev-servicesGETDocker Compose, Testcontainers, and service connection entries
/bootui/api/dev-services/{id}/logsGETBounded log tail for a bean-backed service when available
/bootui/api/dev-services/{id}/restartPOSTRestart a bean-backed service only when explicitly enabled
/bootui/api/data/repositoriesGETDetected Spring Data repositories (summary)
/bootui/api/data/repositories/{name}GETSpring Data repository detail with query methods
/bootui/api/hibernateGETLatest Hibernate/JPA advisor report
/bootui/api/hibernate/scanPOSTRun explicit read-only Hibernate/JPA advisor checks
/bootui/api/hibernate-statisticsGETLive read-only Hibernate SessionFactory statistics (Hibernate Statistics panel)
/bootui/api/hibernate-statistics/enablePOSTEnable Hibernate statistics collection for the current runtime
/bootui/api/database-advisorGETLatest Database advisor report, with per-datasource read status and scan diagnostics
/bootui/api/database-advisor/scanPOSTRun explicit read-only, bounded physical-schema checks
/bootui/api/postgresqlGETLatest PostgreSQL vital-signs report without starting a database read
/bootui/api/postgresql/readPOSTRun an explicit bounded, read-only PostgreSQL statistics read
/bootui/api/mysqlGETLatest cached MySQL report; never opens a connection or runs SQL
/bootui/api/mysql/readPOSTExplicit bounded MySQL operational read through application JDBC datasources
/bootui/api/sql-traceGETRetained SQL execution report and aggregate statistics
/bootui/api/sql-trace/insightsGETRanked normalized statements and request-route attribution over the retained window
/bootui/api/sql-trace/clearPOSTClear the retained SQL execution buffer
/bootui/api/sql-trace/recordingPOSTPause/resume SQL execution capture at runtime
/bootui/api/sql-trace/streamGETSQL Trace change notifications over Server-Sent Events (re-fetch trigger)
/bootui/api/transactionsGETRetained transaction boundary report (unavailable report on Quarkus)
/bootui/api/transactions/clearPOSTClear retained transaction boundaries on Spring MVC and WebFlux
/bootui/api/transactions/recordingPOSTPause/resume transaction capture on Spring MVC and WebFlux
/bootui/api/transactions/streamGETTransaction change notifications over Server-Sent Events on Spring MVC and WebFlux
/bootui/api/architectureGETLatest Architecture scan report
/bootui/api/architecture/scanPOSTRun explicit ArchUnit hygiene checks
/bootui/api/rest-apiGETLatest REST API Advisor scan report
/bootui/api/rest-api/scanPOSTRun explicit read-only REST API best-practice checks
/bootui/api/rest-api/error-contractGETDeclared exception handlers (paged, declaration-only; never invokes a handler)
/bootui/api/springGETLatest Spring Advisor scan report
/bootui/api/spring/scanPOSTRun explicit read-only Spring context and configuration checks
/bootui/api/memoryGETLatest Memory Advisor scan report
/bootui/api/memory/scanPOSTRun explicit read-only JVM memory, GC, and thread health checks
/bootui/api/graalvmGETLatest GraalVM native-image readiness report
/bootui/api/graalvm/scanPOSTRun explicit native-image readiness checks
/bootui/api/graalvm/metadataGETDownload generated reachability metadata scaffold
/bootui/api/graalvm/scan/progressGETProgress of the running GraalVM readiness scan
/bootui/api/graalvm/scan/cancelPOSTCancel the running GraalVM readiness scan
/bootui/api/graalvm/installPOSTWrite the reachability metadata scaffold into the project when running from an exploded build
/bootui/api/graalvm/dockerfileGETDownload a tailored native-image Dockerfile-native
/bootui/api/graalvm/dockerfile/installPOSTWrite Dockerfile-native into the project when running from an exploded build
/bootui/api/graalvm/install/allPOSTWrite both the metadata scaffold and Dockerfile-native
/bootui/api/cracGETLatest CRaC checkpoint/restore readiness report
/bootui/api/crac/scanPOSTRun explicit CRaC readiness checks
/bootui/api/crac/dockerfileGETDownload a tailored Dockerfile-crac
/bootui/api/crac/entrypointGETDownload the matching checkpoint-and-run.sh entrypoint
/bootui/api/crac/dockerfile/installPOSTWrite Dockerfile-crac into the project when running from an exploded build
/bootui/api/crac/entrypoint/installPOSTWrite checkpoint-and-run.sh into the project when running from an exploded build
/bootui/api/crac/install/allPOSTWrite both CRaC container assets
/bootui/api/flyway/migrationsGETFlyway migration state and action availability per database
/bootui/api/flyway/migratePOSTRun pending Flyway migrations only when confirmed, not read-only, and not Modulith-managed
/bootui/api/flyway/cleanPOSTClean Flyway-managed schemas only when confirmed, allowed by Flyway, not read-only, and not Modulith-managed
/bootui/api/liquibase/changesetsGETApplied/pending Liquibase change sets and action availability per database
/bootui/api/liquibase/updatePOSTApply pending Liquibase change sets only when confirmed and not read-only
/bootui/api/cacheGETCache managers, caches, metrics, and annotation operations
/bootui/api/cache/clearPOSTClear one or all known caches only when explicitly enabled and confirmed
/bootui/api/spring-securityGETSpring Security filter chain report
/bootui/api/spring-security/explainGETBest-effort chain match for a method/path
/bootui/api/spring-security/endpointsGETBest-effort per-endpoint authorization report
/bootui/api/security-logsGETRecent Spring Boot audit/security events
/bootui/api/security-logs/streamGETSecurity Logs change notifications over Server-Sent Events (re-fetch trigger)
/bootui/api/securityGETLatest Spring Security Advisor report
/bootui/api/security/scanPOSTRun explicit Spring Security hardening checks
/bootui/api/pentestingGETLatest local OWASP hygiene report
/bootui/api/pentesting/scanPOSTRun explicit bounded loopback OWASP hygiene checks
/bootui/api/dismissed-rulesGETAdvisor rules and findings dismissed in .bootui/boot-ui.yml
/bootui/api/dismissed-rules/{ruleId}POSTDismiss one advisor rule or finding
/bootui/api/dismissed-rules/{ruleId}DELETERestore one dismissed advisor rule or finding
/bootui/api/copilot/dashboardGETSanitized GitHub Copilot CLI activity dashboard
/bootui/api/copilot/**GETSanitized GitHub Copilot CLI session dashboard, token usage, explorer, raw reveal, SSE
/bootui/api/claude-code/dashboardGETSanitized Claude Code activity dashboard
/bootui/api/claude-code/**GETSanitized Claude Code project-log dashboard, token usage, explorer and SSE; raw reveal can be opted into on Spring but is always disabled on Quarkus
/bootui/api/mcp-serverGETMCP Server panel status (enabled state, configured mode, transport, advertised tools)
/bootui/api/mcp-server/togglePOSTEnable/disable the MCP server at runtime, overriding bootui.mcp.enabled
/bootui/api/mcpPOSTLocal-only MCP JSON-RPC 2.0 transport (served only while the server is enabled; status lives at /bootui/api/mcp-server)
/bootui/api/cliGETCommand-line endpoint status and the tool catalog this instance exposes
/bootui/api/cli/tools/{name}POSTInvoke one tool by name and return its payload directly, with the outcome in the HTTP status
/bootui/api/java-agentGETBootUI Java agent attachment, claim, setup, and sensor status
/bootui/api/java-agent/sensors/{id}POSTSwitch threads, files, environment, thread-activity, thread-locals, or security-sinks on or off at run time ({"enabled": true}), until the JVM ends; files is on by default
/bootui/api/code-inventoryGETCode Inventory summary: methods executed of tracked, changes since the previous run, dependency counts
/bootui/api/code-inventory/changesGETPaged methods changed or added since the previous run, each executed or not in this run
/bootui/api/code-inventory/methodsGETPaged application methods by package, class, and status, with package and class counts
/bootui/api/code-inventory/dependenciesGETPaged dependency use: declared jars, classes loaded in this run, startup or later, first route
/bootui/api/code-pathsGETCode Paths summary: sensor status, routes ranked by warm median with top methods, excluded methods
/bootui/api/code-paths/routeGETOne route's call tree, paged by depth, offset, and limit, with callers and reach per method
/bootui/api/code-paths/requests/{requestId}GETOne request's call tree while the run keeps it
/bootui/api/code-paths/beansGETBeans at runtime: observed calls between beans beside the declared dependencies
/bootui/api/code-paths/probesGETCurrent run's bounded method probes and recorded invocations
/bootui/api/code-paths/probesPOSTStart one bounded method probe (the browser confirms first), optionally with browser-only argument/return shapes; blocked by Code Paths/global read-only policy
/bootui/api/code-paths/probes/{id}GETOne method probe; unknown ids return 404
/bootui/api/code-paths/probes/{id}/stopPOSTStop a probe and remove its advice; same action policy as start
/bootui/api/code-paths/probes/{id}DELETEAlias for stopping a method probe, not deleting its recorded evidence
/bootui/api/side-effectsGETSide Effects summary: sensor coverage, frequent rows, dropped records, hooks, limitations
/bootui/api/side-effects/sensorGETOne side-effect sensor's rows, most frequent first, paged by offset and limit
/bootui/api/rest-client-traceGETLatest REST Client report and retained outbound HTTP calls
/bootui/api/rest-client-trace/clearPOSTClear the retained REST client call buffer
/bootui/api/rest-client-trace/recordingPOSTPause/resume REST client call capture at runtime
/bootui/api/rest-client-trace/streamGETREST Client change notifications over Server-Sent Events (re-fetch trigger)
/bootui/api/websocketsGETDeclared WebSocket endpoints, live sessions, STOMP subscriptions, and captured frame metadata
/bootui/api/websocketsDELETEClear captured WebSocket frame metadata and per-session counters
/bootui/api/websockets/capturePOSTPause/resume WebSocket frame-metadata capture at runtime
/bootui/api/websockets/streamGETWebSockets change notifications over Server-Sent Events (re-fetch trigger)
/bootui/api/sql-traceGETCurrent bounded SQL trace snapshot and aggregate statistics
/bootui/api/sql-trace/insightsGETRanked normalized statements and request-route attribution over the retained window
/bootui/api/transactionsGETCurrent bounded transaction-boundary snapshot and aggregate statistics
/bootui/api/activityGETMerged Live Activity stream and KPI summary (params: type, severity, since, limit, plus q, until, cursor, pageSize when persistence is enabled; source (buffers or journal) overrides bootui.activity.feed-source, and the journal's feed also takes route, run, requestId, and noRequest)
/bootui/api/activity/streamGETLive Activity change notifications over Server-Sent Events (re-fetch trigger)
/bootui/api/activity/service-mapGETLive Flow service map of observed and configured dependencies (ServiceMapReport)
/bootui/api/activity/request/{id}GETPer-request profile correlating SQL, exceptions, auth, REST client calls, cache accesses, and trace for one HTTP exchange
/bootui/api/activity/request/{id}/journalGETOne request as the runtime journal recorded it: its work on one timeline, the collections that completed while it ran, its CPU time and allocated bytes, how it compares with its route's p50 and p95, and the tables, transactions, caches, messages, hosts, and log templates it touched
/bootui/api/activity/use-existing-datasourcePOSTHot-switch Live Activity from in-memory to the existing DataSource (confirmation-gated)
/bootui/api/activity/journalGETThe runtime journal's status: retained events and bytes against their bounds, events recorded and dropped per source, and the previous runs whose summaries are kept
/bootui/api/activity/journal/clearPOSTClear recording: drop the run's recorded events and aggregates (confirmation-gated, blocked by read-only policy)
/bootui/api/activity/resourcesGETThe run's resource track and CPU ledger: one point per sweep (process CPU split into requests, thread families, and the JVM's own work; heap; allocation; threads) and the run's totals, or why the sampler does not run
/bootui/api/runtime-insightsGETRuntime Insights: the journal's retained events projected into observations, with the window, per-source correlation coverage, each check's status, limitations, and the declared routes not exercised in this run
/bootui/api/runtime-insights/insights/{id}GETOne observation by its stable id, with up to 20 evidence rows and how many were left out; an unknown id answers unavailable
/bootui/api/runtime-insights/impactGET?symbol=<symbol>: for a route, bean, class, method (Class#method, read from the route trees with the BootUI agent), repository, table, cache, or host, the routes that ran through it in this run, those that did not, and those sharing a resource with it; AMBIGUOUS, NOT_FOUND, or UNAVAILABLE with the reason
/bootui/api/runtime-insights/impact/symbolsGET?query=<text>: at most 20 routes, beans, repositories, tables, caches, hosts, and events of the run's model matching the text, best first, each with its kind, for the change impact box
/bootui/api/runtime-insights/comparisonGETThe current run compared with the application's newest kept run (including listener-only or idle runs; another application sharing the JVM is never compared), or ?run=<runId>: with the BootUI agent, the code changes since the previous run first and the side effects new or gone outside the JVM, then route/execution behavior, new and gone fingerprints and edges from shared sources, adjacent-restart cost, and warm latency last; INSUFFICIENT, NOT_COMPARABLE, NO_PREVIOUS_RUN, or UNAVAILABLE with the reason
/bootui/api/runtime-insights/resource-profileGETThe Profile resources session's state and the last session's CPU samples, allocation, and hot frames by route; starts nothing
/bootui/api/runtime-insights/resource-profilePOSTStart a JFR session bounded by bootui.resources.jfr.max-duration
/bootui/api/runtime-insights/resource-profile/stopPOSTEnd the running session now and return its results
/bootui/api/emailGETCaptured outgoing email summaries and content-policy status
/bootui/api/email/{id}GETOne captured email with its bodies and attachment metadata
/bootui/api/email/{id}/emlGETDownload one captured email as a .eml file
/bootui/api/emailDELETEClear captured emails when not read-only
/bootui/api/kafkaGETBounded Kafka producer and consumer activity
/bootui/api/kafkaDELETEClear retained Kafka activity when not read-only
/bootui/api/rabbitmqGETBounded RabbitMQ publisher and consumer activity
/bootui/api/rabbitmqDELETEClear retained RabbitMQ activity when not read-only
/bootui/api/jmsGETBounded JMS producer and consumer activity
/bootui/api/jmsDELETEClear retained JMS activity when not read-only

6.5 Configuration properties

Prefix:

bootui.*

Initial properties:

PropertyDefaultDescription
bootui.enabledAUTOEnables BootUI. Values: AUTO, ON, OFF (YAML parses ON/OFF as booleans, so true/yes and false/no are accepted too).
bootui.path/bootuiNormalized application-relative UI base path used by shell/assets, filters, and the banner. See §4.2 for validation rules.
bootui.api-path<bootui.path>/apiOptional normalized application-relative API base path used by controllers, filters, MCP, OTLP, streams, and downloads.
bootui.allow-non-localhostfalseExplicitly allow non-loopback requests.
bootui.allowed-hosts(empty)Extra Host header values accepted by the loopback filter (DNS-rebinding allow-list).
bootui.authentication.token(generated)Access token required by API callers outside the shared trusted-source policy. A generated token is logged once at startup when remote access is configured; configured tokens are never logged.
bootui.mask-secretstrueMask secret-like config values.
bootui.expose-valuesMASKEDOne of MASKED, METADATA_ONLY, FULL.
bootui.read-onlyfalseDisable all browser-triggered actions while keeping read-only panel data visible.
bootui.advisors.max-retained-violations10000Positive per-advisor latest-scan detail budget across rules; frozen at scan start. Samples/counts are unchanged; missing retained details are explicit.
bootui.show-bannertruePrint BootUI URL on startup.
bootui.startup.enabledtrueInstall a BufferingApplicationStartup automatically while BootUI is active.
bootui.startup.capacity4096Maximum startup steps retained by BootUI's auto-installed startup buffer.
bootui.enabled-profilesdev,localProfiles that activate BootUI.
bootui.disabled-profilesprod,productionProfiles that disable BootUI unless bootui.enabled=ON.
bootui.overrides-file.bootui/application-bootui.propertiesFile used to persist local runtime configuration overrides; also locates boot-ui.yml.
bootui.monitoring.exclude-selftrueHide BootUI's own runtime data from monitoring panels.
bootui.cache.clear-enabledtrueEnable Cache clear actions after explicit browser confirmation.
bootui.http-sessions.max-sessions50Maximum local embedded Tomcat HTTP sessions listed by the HTTP Sessions panel.
bootui.http-exchanges.max-exchanges200Maximum recent HTTP exchanges retained in memory for the HTTP Exchanges panel.
bootui.http-exchanges.reserved-share-percent25Share of the HTTP exchange buffer reserved for recent 5xx and slow exchanges; 0 disables it.
bootui.email.max-entries100Maximum outgoing emails retained in memory for the Email panel; oldest evicted first.
bootui.email.dev-trapfalseCapture outgoing email without handing it to the real mail transport (MailDev/GreenMail-style trap).
bootui.email.mask-contentfalseMask captured recipients, subject, and bodies unless bootui.expose-values=FULL.
bootui.email.max-body-length200000Maximum characters retained per captured text/HTML body.
bootui.vulnerabilities.osv-enabledtrueAllow the user-initiated OSV.dev vulnerability scan action.
bootui.vulnerabilities.request-timeout10sTimeout applied to each OSV request.
bootui.vulnerabilities.max-packages500Maximum packages sent in one OSV batch query; the excess is reported as scan.packagesSkipped.
bootui.vulnerabilities.max-advisories200Maximum advisory detail documents fetched after a query.
bootui.vulnerabilities.epss-enabledtrueAllow the batched FIRST.org EPSS exploit-probability lookup during a scan.
bootui.vulnerabilities.epss-base-urihttps://api.first.orgBase URI of the FIRST.org EPSS API queried during a scan.
bootui.github.api-enabledtrueAllow GitHub panel refresh calls to GitHub APIs.
bootui.github.request-timeout5sTimeout for each GitHub API request and local gh auth token lookup.
bootui.github.max-pull-requests10Maximum open pull requests returned in one GitHub refresh.
bootui.github.max-issues25Maximum open issues fetched for the issue buckets and open issue list.
bootui.github.max-security-alerts50Maximum Dependabot alert details listed per refresh; count stays exact, metadata only.
bootui.github.max-workflow-runs20Maximum recent workflow runs returned in one GitHub refresh.
bootui.github.quota-safety-threshold10Skip optional GitHub calls when remaining core quota is at or below this value.
bootui.github.max-api-calls17Maximum GitHub API calls issued by one refresh.
bootui.github.allowed-api-hostsapi.github.comAllowed GitHub API hosts; add a GitHub Enterprise host to enable enterprise remotes.
bootui.dev-services.restart-enabledfalseEnables restart controls for bean-backed Testcontainers services.
bootui.dev-services.log-tail-bytes65536Maximum bytes returned by one Dev Services log request.
bootui.telemetry.enabledtrueEnables local trace capture and the OTLP/HTTP receiver used by the Traces and AI Framework panels.
bootui.telemetry.max-traces500Maximum distinct traces retained in memory; internally capped for UI safety.
bootui.telemetry.max-spans-per-trace500Maximum spans retained for one trace; internally capped for UI safety.
bootui.telemetry.max-attribute-value-bytes4096Maximum attribute string length before truncation; internally capped for UI safety.
bootui.telemetry.exclude-self-spanstrueDrops ingested spans for BootUI's own API routes before they enter the local trace store.
bootui.telemetry.enrichtrueStamp BootUI bootui.* attributes (service identity, SQL query count / suspected N+1, exceptions) on the active span at BootUI's capture points; effective only while bootui.telemetry.enabled is on.
bootui.telemetry.max-request-bytes8388608Maximum OTLP payload size accepted by the local receiver.
bootui.ai.token-series-minutes60Default token-usage chart window for the AI Framework panel, capped by the API.
bootui.ai.max-recent-chats100Maximum recent chat rows surfaced by the AI Framework panel, capped by the API.
bootui.ai.show-content-capture-bannertrueShows guidance when Spring AI or LangChain4j prompt/completion content is not captured in spans.
bootui.copilot.enabledAUTOEnable the Copilot panel when local Copilot CLI session state exists.
bootui.copilot.session-state-dir~/.copilot/session-stateDirectory scanned for Copilot CLI session directories and events.jsonl files.
bootui.copilot.max-events-per-session2000Maximum Copilot events retained per parsed session.
bootui.copilot.max-sessions100Maximum recent sessions returned by the Copilot session explorer.
bootui.copilot.max-parsed-sessions100Maximum recent Copilot session files parsed and retained in memory.
bootui.copilot.stream-debounce400msDebounce window before refreshing parsed Copilot sessions and notifying stream subscribers.
bootui.copilot.allow-raw-revealtrueAllows opt-in raw Copilot event JSON reveal on loopback.
bootui.claude-code.enabledAUTOEnable the Claude Code panel when local Claude Code project logs exist.
bootui.claude-code.session-state-dir~/.claude/projectsDirectory scanned for Claude Code project JSONL logs.
bootui.claude-code.max-events-per-session2000Maximum Claude Code events retained per parsed session.
bootui.claude-code.max-sessions100Maximum recent sessions returned by the Claude Code session explorer.
bootui.claude-code.max-parsed-sessions100Maximum recent Claude Code JSONL files parsed and retained in memory.
bootui.claude-code.stream-debounce400msDebounce window before refreshing parsed Claude Code sessions and notifying stream subscribers.
bootui.claude-code.allow-raw-revealfalseAllows opt-in raw Claude Code JSONL reveal on Spring; Quarkus always disables it. Logs can include content.
bootui.mcp.enabledOFFInitial state of the local-only MCP server for AI agents. OFF/AUTO start it disabled; ON starts it enabled. The MCP Server panel can toggle it at runtime, overriding this value.
bootui.mcp.max-results200Maximum items returned by paginated MCP read tools (config, beans, mappings, logs, traces, etc.).
bootui.mcp.max-payload-bytes1048576Maximum incoming JSON-RPC request bytes, enforced before full body materialization.
bootui.mcp.max-concurrent-calls20Maximum concurrent tool calls; additional calls fail immediately with server-defined error -32001.
bootui.mcp.execution-timeout30sMaximum wall-clock duration of a tool call; timeouts return server-defined error -32002.
bootui.mcp.max-response-bytes4194304Maximum rendered JSON-RPC response bytes; oversized responses return server-defined error -32003.
bootui.cli.enabledtrueWhether the command-line endpoint answers. When false, the catalog reports itself disabled and tool invocation returns 503.
bootui.cli.max-results200Maximum items returned by paginated command-line read tools, tracked separately from the MCP cap.
bootui.cli.max-concurrent-calls20Maximum concurrent command-line tool calls; additional calls are refused with 429.
bootui.cli.execution-timeout30sMaximum wall-clock duration of a command-line tool call; timeouts return 504.

Every visible panel must support bootui.panels.<panel-id>.enabled; panels with mutating browser actions must also support bootui.panels.<panel-id>.read-only. These properties are specified panel-by-panel in PROPERTIES.md.

6.6 Security model

BootUI must be secure by default.

Rules:

  • Bind to local development only.
  • Reject non-loopback requests by default.
  • When non-loopback access is explicitly enabled, leave the static SPA shell public but require a bearer token for API callers outside LocalhostGuard's trusted-source policy. Loopback, configured bootui.trusted-proxies CIDRs, and a gateway trusted by bootui.trust-container-gateway bypass token authentication; bootui.allow-non-localhost=true alone does not make a source trusted. Host and cross-site-write checks still apply. The SPA exchanges the startup token for an HTTP-only, same-site session cookie so SSE and downloads work; programmatic clients use the standard HTTP bearer authorization scheme.
  • Validate the Host header against the built-in loopback names plus bootui.allowed-hosts to defend against DNS-rebinding attacks, rejecting a present but malformed authority the same way as a disallowed hostname (only a genuinely missing or blank Host is allowed), and reject cross-site state-changing requests (via Origin/Sec-Fetch-Site) so mutating endpoints stay protected even when Spring Security is absent.
  • If Spring Security is present while BootUI is active, contribute a highest-precedence /bootui/** permit-all security chain and log a warning so the developer console stays directly reachable. This must not weaken the localhost-only servlet filter unless bootui.allow-non-localhost=true is explicitly set.
  • Disable in production profile by default.
  • Mask secret-like values by default.
  • Never display .env contents.
  • Never write configuration values back to application source files.
  • Persist runtime overrides only to BootUI's configured override file.
  • Never forward telemetry off-process by default.
  • Never proxy arbitrary external URLs.
  • Never perform dependency vulnerability lookups until the developer explicitly starts an OSV scan.

Production safety:

  • If BootUI detects a likely production environment, it should disable itself and log a clear message.
  • Explicit override should be intentionally named, for example:
bootui.enabled=ON
bootui.allow-non-localhost=true

The second property should be required to expose BootUI beyond localhost.

6.7 MCP server for AI agents

BootUI optionally exposes its advisors and read-only diagnostics to local AI coding agents (GitHub Copilot, Claude Code) through a Model Context Protocol server, enabling an agent to consult the advisors before proposing a fix and to pull runtime diagnostics (exceptions, security logs, SQL traces, HTTP exchanges, traces) while diagnosing an issue. The headless JSON-RPC integration is paired with an MCP Server panel (top of the Developer Tools group) that documents what the server exposes and provides a runtime on/off toggle.

Design rules:

  • Opt-in and fail-closed. Disabled by default. bootui.mcp.enabled=ON starts it enabled; OFF (default) and AUTO start it disabled. BootUI's own activation condition keeps it confined to dev contexts, so it is never reachable in production.

  • Runtime toggle that overrides configuration. The MCP server beans are always registered while BootUI is active, but the transport only serves requests while the server is enabled. The live state is initialized from bootui.mcp.enabled and can be flipped at runtime from the MCP Server panel (POST /bootui/api/mcp-server/toggle), overriding the configured property for the lifetime of the running application. While disabled, JSON-RPC requests are refused in-band with a server disabled error.

  • In-process and dependency-light. Implemented as a hand-rolled JSON-RPC 2.0 server (initialize, ping, server/discover, tools/list, tools/call, prompts/list, prompts/get) served over the existing HTTP stack at POST /bootui/api/mcp, with a GET /bootui/api/mcp-server status response for human inspection. The transport endpoint itself returns 405 to GET because BootUI offers no server-to-client stream, which MCP 2026-07-28 removed. No new runtime dependencies beyond what BootUI already ships. The Spring AI MCP server starter is intentionally not used because it targets Spring Boot 3.x.

  • Deliberately scoped method surface. The server advertises only the tools and prompts capabilities. resources/* and completion/complete are intentionally not implemented: every piece of runtime data BootUI exposes is already reachable as a bounded, arguments-driven tool call, so a parallel MCP resource surface would duplicate the tool catalog without adding capability. This is a scope decision, not an omission, and is revisited if a client-side use case (e.g. a client that only walks resources/list) requires it.

  • Detail-free internal errors. Unexpected dispatch, tool, policy, or result-serialization failures return the standard JSON-RPC internal error (-32603, message Internal error) without exception text or debug fields. The original throwable and stack trace are logged once on the server; expected validation, disabled-server, panel-policy, and unknown-method/tool errors retain their specific safe messages.

  • Client errors are the request's fault, not the server's. Tools delegate to the same handlers the REST API uses, and those handlers signal a client error by throwing their framework's own exception (ResponseStatusException on Spring, WebApplicationException on Quarkus). Each adapter translates a 4xx failure into the framework-neutral McpToolClientException at its tool-registration boundary, and the dispatcher reports it in-band (isError: true) with the same reason the REST API returns, carrying the canonical status for non-MCP consumers of the same outcome. Such a refusal is never logged as a server failure. A 5xx failure keeps its original throwable and remains a detail-free -32603. The translation is framework-specific but the resulting behaviour is identical on Spring MVC, Spring WebFlux, and Quarkus.

  • Reuse, don't reimplement. Each tool delegates to the same controller/service the REST API and panels use and returns the existing DTO records, so contracts stay stable and masked.

  • Single-flight parity. Advisor action tools share the same per-scanner admission as REST. A duplicate tools/call remains an HTTP 200 MCP response but returns an in-band tool error (isError: true) with the canonical busy message. Panel disabled/read-only policy is checked first, and the aggregate MCP concurrent-call cap remains a separate capacity limit.

  • Tool surface. Every available panel with a safe passive read has an MCP read tool:

    • Advisor actions: architecture_scan, spring_scan, hibernate_scan, database_advisor_scan, memory_scan, security_scan, pentest_scan, rest_api_scan, graalvm_scan, crac_scan, and vulnerabilities_scan.
    • Cached advisor reports: get_architecture_report, get_spring_report, get_hibernate_report, get_database_advisor_report, get_memory_report, get_security_report, get_pentest_report, get_rest_api_report, get_graalvm_report, get_crac_report, and get_vulnerabilities_report.
    • Runtime Insights: get_runtime_insights, get_runtime_insight, get_runtime_impact, and get_runtime_run_comparison.
    • Diagnostics: get_live_activity, get_request_profile, get_exceptions, get_exception_detail, get_security_logs, get_sql_traces, get_transactions, get_traces, get_log_tail, get_http_exchanges, get_http_routes, and get_rest_client_traces.
    • Runtime and integration reads: get_overview, get_health, get_config, get_beans, get_mappings, get_loggers, get_conditions, get_http_sessions, get_scheduled_tasks, get_fault_tolerance, get_cache_stats, get_database_connection_pools, get_postgresql_report, get_mysql_report, get_metrics, get_live_memory, get_jvm_tuning, get_heap_dump_report, get_threads, get_startup_timeline, get_profile_diff, get_spring_data_repositories, get_flyway_migrations, get_liquibase_changesets, get_spring_security, get_ai_overview, get_emails, get_kafka_activity, get_rabbitmq_activity, get_jms_activity, get_agent_status, get_code_inventory, get_code_paths, get_method_probe, get_side_effects, get_devtools_status, get_dev_services, get_github_dashboard, get_copilot_sessions, get_claude_code_sessions, get_hibernate_statistics, and get_websockets.
    • Advisor violation pages: get_architecture_rule_violations, get_spring_rule_violations, get_hibernate_rule_violations, get_database_advisor_rule_violations, get_memory_rule_violations, get_security_rule_violations, and get_rest_api_rule_violations (see Advisor violation pages above).
    • Bounded actions: clear_exceptions, clear_sql_traces, pause_sql_trace_recording, resume_sql_trace_recording, clear_transactions, pause_transaction_recording, resume_transaction_recording, clear_traces, clear_rest_client_traces, pause_rest_client_recording, resume_rest_client_recording, postgresql_read, mysql_read, analyze_heap_dump, trigger_devtools_livereload, and start_method_probe, which retransforms one application method for at most 20 invocations or 60 seconds and needs the user's separate approval (refused by read-only policy, metadata only in every exposure mode).

    Java Agent exposes actions enable_agent_sensor and disable_agent_sensor for the switchable runtime sensors; each requires a sensor id and is refused by the Java Agent panel's read-only policy. Agents must ask the user before calling either action. get_agent_status verifies the result.

    MySQL (§5.17.8) exposes cached read get_mysql_report and action mysql_read, both argument-free, on MVC/WebFlux/Quarkus with a supported JDBC datasource. The generated CLI equivalents are bootui db mysql report and bootui db mysql read. Agents must request approval before active collection and interpret partial evidence and server scope rather than applying advisor-score semantics.

    Heap capture/download, HTTP probes, database/cache mutations, GitHub writes, dev-service restarts, and arbitrary agent commands are deliberately excluded, as are Profile resources (JFR), enabling Hibernate statistics, the WebSockets capture switch, and logger-level changes, which stay browser-only. Java agent sensor switches are bounded MCP/CLI actions, guarded by the Java Agent panel's enabled and read-only policy. Tools whose backing controller is absent or not applicable to the running stack are not advertised; a tools/call naming one of them answers -32602 with Tool not available in this application: <name>. and the panel's unavailable reason, also in error.data (tool, panel, reason), while a name outside the catalog keeps Unknown tool: <name>. The CLI facade answers both with 404. A catalog tool whose panel is disabled gets the in-band disabled refusal instead.

  • Tool hints and argument schemas. Each tools/list entry carries MCP annotations derived from the catalog's action flag: reads are readOnlyHint/idempotentHint; clear_* actions are destructiveHint; clear, pause, and resume are idempotentHint; vulnerabilities_scan is openWorldHint. inputSchema describes each argument per tool from the shared tool guide (id source, query words, an example) with its effective default page size.

  • Strict inputs. The transport rejects invalid JSON-RPC id/params types, non-object tool arguments, unknown arguments, and values whose type does not match the advertised schema with -32600/-32602; malformed values are never silently coerced or replaced with broad defaults.

  • Dual-era protocol. The endpoint serves MCP 2025-06-18 and MCP 2026-07-28 at once and chooses per request, as MCP 2026-07-28's backward compatibility rules describe (AI agents). A request without _meta["io.modelcontextprotocol/protocolVersion"], and every initialize, is legacy and answers byte for byte as before: an absent MCP-Protocol-Version header means 2025-06-18, and any value other than 2025-06-18 or 2026-07-28, or a repeated header, is 400/-32600; 2026-07-28 without the modern _meta is a malformed modern request, 400/-32602 echoing the request id. The header is judged after the body is read, and the envelope fields jsonrpc, method, and params.name count only when they are strings, so all three stacks answer a null or numeric field with the same client error. A modern request is validated (a string or integer id, never null, fractional, or another type, and present unless the method is a notifications/ one, so a modern tools/call never runs unanswered; judged for any request with a non-2025-06-18 or non-string _meta version, or a lone 2026-07-28 header; version type, header agreement, supported version, client capabilities, Mcp-Method, Mcp-Name with Base64 decoding, progress token type) with 400 and -32602, -32020, or -32022 carrying data.supported. The modern era has server/discover but no initialize or ping, answers an unknown method with 404, adds resultType, _meta server identity, and cache hints (ttlMs: 60000, cacheScope: "private") on discovery and list results, and moves BootUI's server errors to -31000..-31003 because MCP 2026-07-28 reserves -32000..-32099. Era selection precedes the disabled short-circuit, so a malformed modern request is a 400 even while the server is off. The CLI facade and the engine keep the legacy codes.

  • Request-scoped progress (both eras). A tools/call with a string or integer _meta.progressToken, to a tool whose operation reports measured phases through the engine's OperationProgress (architecture_scan and vulnerabilities_scan), from a client whose Accept explicitly lists text/event-stream, answers on a text/event-stream POST response with X-Accel-Buffering: no: rate-limited notifications/progress (burst 8, then one per 250 ms, coalescing to the newest, flushed before the end, each dropped when it would exceed bootui.mcp.max-response-bytes) and exactly one final response, after which the stream closes; a string progress token is at most 128 characters (refused when modern, ignored when legacy). Events are data: lines with no ids; keep-alive comments every 2 seconds, so Spring MVC, which only notices a closed stream when a write fails, does so within about 4 seconds; WebFlux and Quarkus notice it at once. Closing a modern stream cancels the call: nothing more is written, the tool is interrupted and stops at its next step, and its concurrency permit is released exactly once, when the tool has returned and the stream is written. Only a streamed call is cancelled by a disconnect, including one whose client left before the stream started; a blocking JSON call runs to its end, its timeout, or a legacy notifications/cancelled. A cancelled call is counted in the /mcp-server status's cancellations, apart from timeouts (and in callCount), while a server fault during writing is reported as a fault; the status also lists supportedProtocolVersions. The execution timeout stays absolute, and a call that timed out before its stream opened still ends with the timeout response. Each event is one line of compact JSON regardless of the application's mapper configuration, built through one engine helper that refuses a line break. A writer blocked on a client that stops reading keeps the permit (WebFlux emits only on subscriber demand) and gives up 10 seconds after the execution timeout on every stack; Quarkus then resets the response. Spring MVC falls back to one JSON response when the request cannot go async. A legacy stream ends with a legacy final response, and closing it does not cancel the call, because MCP 2025-06-18 says "Disconnection SHOULD NOT be interpreted as the client cancelling its request": the tool runs on within the timeout (a client gone before the stream starts releases the call, since nothing began). A legacy notifications/cancelled naming an in-flight tools/call id (matched by value) is answered 202 and cancels it; a cancelled stream closes without a final response and a cancelled blocking call answers -32800. Unknown, finished, or malformed cancellations are ignored. With no sessions in MCP 2025-06-18, any local caller that passes the transport checks can cancel a request whose id it knows, including by accident: a late cancellation from one client can cancel another client's in-flight call with the same id, whose stream then ends with no response (an accepted limitation; an Mcp-Session-Id would change the legacy bytes). Ids two in-flight calls share are never cancelled, fractional ids are matched by their double value only when it is whole, non-zero, and below 2^53, the reason is only logged at debug level, and the in-flight registry holds at most one entry per permit in use, removed before that permit is released. A legacy request is never rejected because of its token, and one without a usable token answers byte for byte as before. Everything else and every refusal is one JSON response; there is still no GET stream, live push, resource, or subscriptions/listen.

  • Agent guidance. Initialization instructions direct agents to establish overview/health context, prefer the smallest relevant read, correlate exception and trace identifiers, verify advisor findings before changing code, and account for active scan costs (memory_scan may trigger a full GC; pentest_scan sends bounded loopback probes). Tool descriptions state intended use, ordering, bounded/snapshot semantics, and sensitive-data caveats. Output schemas identify the corresponding structured BootUI result; the existing panel DTO remains the authoritative shape. Paged reads share one envelope whose total counts every item before the query and filters are applied and whose matched counts what they kept, so a non-zero total beside matched: 0 is an empty query result rather than absent data; tool guidance states that distinction where a narrow query would otherwise be read as a missing value.

  • Prompt surface. prompts/list advertises four argument-free workflows: diagnose_runtime_issue for evidence-led runtime diagnosis, verify_after_change to run the tests, compare with the previous run, and stop, review_application for a focused advisor review, and assess_application for a capability-aware application assessment and prioritized action plan. prompts/get returns the selected workflow as a user message, without executing scans or changes. All prompts distinguish evidence from hypotheses and avoid blind fixes. The assessment starts with existing evidence, declares collection budgets, asks for an explicit fresh-scan scope (separate approval for GC, loopback probes, external vulnerability queries, and database metadata inspection), and reports unavailable, skipped, failed, stale, partial, or insufficient evidence honestly. Its versioned plan records context, coverage, stable action IDs with evidence/risks/acceptance criteria, and an explicit approval stop. Execution belongs to the external coding agent under its host's permissions: only approved actions may proceed, changed context requires renewed approval, and a retained sanitized baseline supports reassessment after restart. This adds no assessment tool, scheduler, browser approval interface, or code-execution endpoint. The BootUI skill teaches the same workflow through existing MCP/CLI tools; see AI agents.

  • Same safety model as the panels. The endpoint sits behind LocalhostOnlyFilter (loopback source, Host allow-list, cross-site write protection). The dispatcher enforces per-panel access: read tools require the backing panel to be enabled, action tools are additionally refused when the panel is read-only or bootui.read-only=true. Configuration values and exception and log messages flow through the same secret masking and bootui.expose-values mode, and paginated reads are bounded by bootui.mcp.max-results. Request, concurrent-call, execution-time, and rendered-response budgets prevent a client from monopolizing local resources. The MCP Server status reports completed call count, aggregate latency, capacity refusals, timeouts, and response-limit refusals. Application-controlled logs, SQL, traces, and exception messages cannot be generically guaranteed secret-free, so initialization and tool guidance explicitly keep that data in the local diagnostic context.

6.8 Command-line endpoint

The same tool registry the MCP server exposes, projected onto plain REST at GET /bootui/api/cli and POST /bootui/api/cli/tools/{name}, so a terminal or a CI job can ask a running application one diagnostic question without an MCP client or a hand-rolled request. It is a transport, not a second capability.

Design rules:

  • A projection, not a reimplementation. Invocation builds the same tools/call request the JSON-RPC transport does and hands it to the same dispatcher logic, so unknown tool, unexpected argument, missing id, disabled panel, action on a read-only panel, result capping, concurrency, and execution timeout are all the existing code path. A shared catalog in the engine declares every tool's name, argument schema, backing panel, and action flag, and each adapter's registry is pinned to it by test, so the two surfaces cannot drift apart. The request body is passed through verbatim rather than bound to a fixed argument record, so a property no tool declares is refused rather than dropped during decoding: a misspelled filter returns 400, never a 200 carrying a report that ignored it.
  • Independent of the MCP toggle. The endpoint is enabled by bootui.cli.enabled (default true) and never requires bootui.mcp.enabled. It is a new route over data the panel endpoints already serve, not a new capability, so it defaults on; requiring a toggle would simply move the prerequisite it exists to remove.
  • The outcome is the HTTP status. MCP reports refused gates in-band (200 with isError), which a shell cannot branch on. The command-line endpoint returns the tool payload directly on success, and otherwise 400 for an invalid argument, 403 for a disabled or read-only panel, 404 for an unknown tool, 409 for an action already running, 429 at capacity, 504 on timeout, and 503 when the endpoint is disabled. Policy is unchanged; only the rendering differs.
  • Self-describing. GET /bootui/api/cli reports the tools this instance advertises, the bootui command each is reached by, their argument schemas, and live panel enable/read-only state, so a client built against one BootUI version stays correct against another — including the command spelling, since the command table lives in the engine and is served rather than compiled into the client. It answers while disabled too, with an empty tool list, so a client can explain the refusal instead of failing opaquely.
  • Separate counters. The facade dispatches through its own instance, so command-line traffic is never counted as agent activity in the MCP Server panel's call, latency, and refusal statistics.
  • Same safety model as the panels. The endpoint sits under bootui.api-path behind the shared loopback, Host allow-list, cross-site-write, and bootui.authentication.token protections, and is exempt from SPA CSRF for the same reason the MCP endpoint is: a non-browser client has no session to carry a token. Read tools invoked over POST remain available under global read-only, because global read-only governs writes and every action tool is still refused by the per-panel check.
  • Available on all three stacks. Spring MVC, Spring WebFlux, and Quarkus serve the identical contract, pinned by a shared conformance suite.

6.9 The bootui command-line interface

bootui-cli is the terminal front-end over that endpoint: one subcommand per tool, --json for the exact payload, and an exit code a script can branch on. The transport underneath it is the dependency-free io.github.jdubois.bootui.client package of the same artifact, so a future Maven plugin or third-party tooling has the same foundation without inheriting a CLI's argument parsing: picocli is an optional dependency of bootui-cli, and the runnable CLI is the shaded bootui-cli-<version>-all.jar, never the thin jar.

Design rules:

  • The command tree is generated, not written. bootui-tools.json is produced at build time from the engine's tool catalog plus a tool name -> command path table, checked in, and the picocli tree is built by iterating it. A test regenerates the file and fails when the checked-in copy is stale, and asserts the path table is total and injective over the catalog. Adding an MCP tool without giving it a command therefore fails the build, and a hand-written command for a tool that no longer exists cannot survive either. A second test runs every command against a stub and asserts it reaches the tool it claims to, so a tree that builds but shadows a leaf is caught as well.
  • No compile-time coupling to BootUI's types. The client package depends on nothing — not BootUI's DTOs, not Jackson, not picocli, not an HTTP library beyond the JDK's, enforced by ClientDependencyTests — and treats payloads as opaque JSON re-emitted verbatim. A CLI from one release has to keep working against an application running another, which rules out sharing DTO records with it. The engine is a test-scoped dependency of bootui-cli, used only to generate the manifest.
  • Runtime discovery is authoritative. The bundled manifest exists so --help works with nothing running. What a given application actually exposes comes from GET /bootui/api/cli, which bootui tools prints: stacks advertise different tool sets, some tools appear only when a library is present, and panels can be disabled.
  • Refusal is not failure. Exit 0 means the tool answered, 1 a usage, authentication, or transport error, and 2 that BootUI declined — a disabled panel, or an action on a read-only one. That distinction is the reason the endpoint puts the outcome in the status code, and dropping it would force CI to parse error text. A rejected token is deliberately 1 and not 2: it says nothing about how the target's panels are configured, so a job that treats 2 as "skip this check" must not swallow it.
  • Exact output when it is being parsed. Output is the server's bytes verbatim with --json or when stdout is not a terminal, and a rendered table or tree otherwise. The rendering infers structure from shape because the payload is opaque, so it is explicitly best-effort; --json is the stable form. Rendered output replaces control characters, because payloads carry application-controlled text and a terminal would otherwise act on an escape sequence found in a log line, an exception message, or a header value.
  • Reflection-free. The command tree is built programmatically and the CLI's only runtime dependency is picocli, so a future native-image build stays a build-file change rather than a rewrite.

7. UX specification

7.1 Navigation

Top-level navigation. Home is pinned at the top of the sidebar without a group header; every other group is collapsible:

  • Home:
    • Scorecard.
    • Live Activity.
    • Runtime Insights.
  • Advisors:
    • Architecture.
    • REST API.
    • Spring.
    • Database.
    • Hibernate.
    • Memory.
    • Security.
    • Pentesting.
    • Vulnerabilities.
  • Runtime:
    • Health.
    • HTTP Sessions.
    • Metrics.
    • Live Memory.
    • JVM Tuning.
    • Heap Dump.
    • Threads.
    • Startup Timeline.
    • GraalVM.
    • CRaC.
  • Configuration:
    • Configuration.
    • Profile Diff.
    • Loggers.
    • Beans.
    • Conditions.
    • Mappings.
  • Database:
    • Database Connection Pools.
    • PostgreSQL.
    • MySQL.
    • SQL Trace.
    • Transactions.
    • Hibernate Statistics.
    • Spring Data.
    • Flyway.
    • Liquibase.
  • Security:
    • Spring Security.
    • Security Logs.
  • Services:
    • Scheduled Tasks.
    • REST Client.
    • Fault Tolerance.
    • WebSockets.
    • AI Framework.
    • Cache.
    • Email.
    • Kafka.
    • RabbitMQ.
    • JMS.
  • Diagnostics:
    • Traces.
    • Log Tail.
    • Exceptions.
    • HTTP Exchanges.
    • HTTP Probe.
  • Instrumentation:
    • Java Agent.
    • Code Paths.
    • Code Inventory.
    • Side Effects.
  • Developer tools:
    • MCP Server.
    • Command Line.
    • Spring DevTools.
    • Dev Services.
    • Copilot.
    • Claude Code.
    • GitHub.
  • Disabled / unavailable:
    • Panels outside Home whose backing infrastructure is unavailable, or that configuration disables. A Java agent panel that is unavailable only because the agent is not attached stays in the Instrumentation group, dimmed, with its reason.

7.2 UI principles

  • Search first.
  • Explain before dumping raw JSON.
  • Always provide the original raw detail behind a disclosure panel.
  • Show "why unavailable" messages with actionable fixes.
  • Use badges for status:
    • Healthy.
    • Warning.
    • Error.
    • Unavailable.
    • Disabled.
  • Never surprise users with network calls or mutations.

7.3 Empty states

Examples:

  • No Actuator health details:
    • "Health details are hidden. In local development, set management.endpoint.health.show-details=always."
  • No Actuator health endpoint:
    • "The Health panel is disabled until a Spring Boot Actuator HealthEndpoint is available."
  • Only default health contributors:
    • "Only Spring Boot default health indicators are available. Add application or dependency health contributors."
  • No startup timeline:
    • "Startup data is unavailable. Leave bootui.startup.enabled=true and bootui.startup.capacity greater than zero, or provide your own BufferingApplicationStartup."
  • No mappings:
    • "No web mappings found. This may be a non-web application."

8. Compatibility

Current compatibility:

  • Java 17 or later.
  • Spring Boot 4.x.
  • Quarkus 3.x.
  • Maven first.
  • Spring MVC, Spring WebFlux, and Quarkus web applications.
  • macOS/Linux/Windows compatible.

9. Testing strategy

9.1 Unit tests

  • Secret masking.
  • Activation rules.
  • Configuration properties binding.
  • DTO mapping.
  • Condition message normalization.
  • Endpoint availability handling.

9.2 Slice tests

  • MVC endpoints for BootUI API.
  • Error responses.
  • Logger update endpoint.
  • Localhost request filtering.

9.3 Integration tests

  • Sample app starts with BootUI enabled.
  • BootUI UI assets are served.
  • BootUI API returns overview.
  • Beans, conditions, env, mappings, health, loggers work against real Spring Boot context.
  • Newer panels work against the sample app or degrade cleanly when optional infrastructure is absent.
  • Production profile disables BootUI.

9.4 Cross-runtime API conformance

  • bootui-conformance runs one black-box HTTP contract against Spring MVC, Spring WebFlux, and Quarkus.
  • Golden panel fixtures continue to pin panel ids, titles, ordering, and action-capable metadata.
  • Available data panels are checked through a central DTO-family catalog. The contract asserts stable field types, null/empty and availability semantics, pagination containers, scan-status fields, and observable secret masking while allowing runtime counts, timestamps, framework versions, and captured data to vary.
  • A central mutation catalog lists every panel action plus the MCP, dismissed-rules, and OTLP infrastructure writes. It classifies non-panel writes by global read-only applicability: dismissed-rule persistence is blocked, the MCP bridge delegates authorization to its per-tool panel policy, and OTLP ingestion remains an observability transport. Adapter access-filter tests consume that catalog so a browser mutation cannot silently bypass global read-only policy. The live contract covers confirmation gates, canonical panel denial, missing targets, single-flight 409 responses, and only deterministic repeatable successes; it never calls external services or invokes destructive, heap-capture, or GC-heavy actions (the Live Memory offload is cataloged for the read-only and access-filter checks but not invoked live, because it would erase other suites' captured data).
  • The same suite runs at the default mount and at independent custom UI/API mounts (including each runtime's host root path), so shell, assets, reads, streams, downloads, errors, and safe writes share one path contract.

9.5 Browser/UI tests

  • Playwright smoke tests for all visible panels in bootui-spring-sample-app/e2e.
  • Search and filter behavior.
  • Masked values stay masked.
  • Empty states are readable.

10. Acceptance criteria for the 1.0 release surface

BootUI's 1.0 release surface is complete when:

  • A sample Spring Boot app can add the starter and open /bootui.
  • The UI shows the complete grouped panel inventory defined in §7.1, plus the Disabled / unavailable navigation group for panels whose backing infrastructure is unavailable.
  • Secret-like values are masked.
  • BootUI is disabled by default outside local/dev contexts.
  • Tests verify activation and safety behavior.
  • Documentation explains installation, activation, safety model, and limitations.

11. Release decisions

Resolved for 1.0.0:

  1. Harden every visible panel and ship the full current route set as supported stable local-development functionality.
  2. Publish release artifacts to Maven Central through the Release workflow so module versions, the README install snippet, tags, release notes, and Central publishing stay synchronized.
  3. Keep optional panels visible and show clear unavailable/empty states when their classpath or data source is absent.
  4. Continue using in-process Actuator endpoint beans and Spring-managed metadata for the 1.x line; revisit broader metadata abstractions after the stable starter surface settles.
Prev
Repository
Next
Implementation plan

© 2026 Julien Dubois