Friday, April 24, 2026

MicroProfile Dashboard Addon

Following up on the OpenAPI GUI Addon, this post introduces a companion addon for the operational side: a self-contained MicroProfile Dashboard that gives you live health checks, JVM metrics with sparkline history, and a searchable config viewer — all from a single HTML page, with no external dependencies.

Why Another Addon?

The OpenAPI GUI Addon solved the API documentation problem. But once your APIs are running, you need to know how they're running. WildFly exposes health and metrics on a separate management port (9990), which isn't always accessible from your browser. And MicroProfile Config has no built-in REST endpoint at all.

The MicroProfile Dashboard Addon bridges this gap: drop it into your WAR and get a full operational dashboard at /dashboard — health, metrics, config, all in one place.

What You Get

  • Health Checks — server and application checks merged into one view, grouped by dynamic categories
  • JVM Metrics — CPU, memory (with heap progress bar), threads, GC stats
  • Sparkline History — CSS-based bar charts for CPU load, heap, and threads over the last 30 minutes, polled every 10 seconds
  • MicroProfile Config — searchable table of all active properties with sensitive value masking
  • Dark/Light Mode — toggle with localStorage persistence
  • Project-Stage Gating — same pattern as the OpenAPI addon: disabled in production by default

Quick Start

Add the dependency:

<dependency>
    <groupId>org.os890.mp-ext</groupId>
    <artifactId>mp-dashboard-addon</artifactId>
    <version>1.0.0</version>
</dependency>

That's it. The dashboard auto-discovers via JAX-RS and is available at /<context-root>/dashboard.

Custom Health Check Categories

The dashboard dynamically discovers categories via the @DashboardCategory annotation. WildFly's built-in checks (server state, deployments, boot errors) automatically get the "Server" category. Your application checks get whatever category you assign:

import org.os890.mp.dashboard.DashboardCategory;

@Readiness
@ApplicationScoped
@DashboardCategory("Database")
public class DatabaseHealthCheck implements HealthCheck {

    @Override
    public HealthCheckResponse call() {
        return HealthCheckResponse.named("PostgreSQL Connection")
                .up()
                .withData("pool-size", getPoolSize())
                .build();
    }
    //...
}

The dashboard shows filter tabs — Server (default view), then your categories alphabetically. Each category gets a status badge (ALL UP / ISSUES) so you can spot problems at a glance.

Metrics Without MicroProfile Metrics

WildFly 39 doesn't ship MicroProfile Metrics — it uses its own metrics subsystem with a Prometheus-format endpoint on the management port. The addon fetches and parses this internally, exposing it as a clean JSON API at /dashboard/api/metrics. No management port access needed from the browser.

The JVM metrics section shows:

  • CPU & Runtime — processors, CPU load %, system load, uptime
  • Memory — heap used/max with visual progress bar, non-heap usage
  • Threads & Classes — active, daemon, peak threads; loaded/unloaded classes
  • Garbage Collection — collections count and cumulative time per collector

Sparkline History

Below the metric cards, three sparkline charts track CPU load, heap memory, and thread count over the last 30 minutes. The charts are pure CSS bar elements — no JavaScript charting library needed. They poll every 10 seconds and color-code bars based on thresholds (green → yellow → red).

Config Viewer

MicroProfile Config has no built-in REST endpoint. The addon adds one at /dashboard/api/config that lists all active properties sorted alphabetically. The dashboard renders them in a searchable table with a "Showing X of Y" counter. Values containing password, secret, token, or credential in the key name are automatically masked.

Project-Stage Gating

Same approach as the OpenAPI GUI Addon: a @PreMatching JAX-RS filter controls access based on config:

  • dashboard.enabled=true/false — explicit override, takes precedence
  • project.stage=production — disables the dashboard when dashboard.enabled is not set

Or use the Maven profile approach from the OpenAPI addon: place the dependency in a -Pdevelopment profile so the production WAR has zero dashboard code.

Technical Details

The addon is a single JAR with no external dependencies — just Jakarta EE 11 and MicroProfile APIs (all provided by WildFly). It contains:

  • DashboardService — serves the HTML page at /dashboard
  • HealthResource — merges WildFly management health data with CDI-injected app checks and @DashboardCategory metadata
  • MetricsResource — parses WildFly's Prometheus metrics into JSON
  • ConfigResource — lists all MicroProfile Config entries with sensitive value masking
  • DashboardAccessFilter@PreMatching filter for project-stage gating
  • DashboardCategory — annotation for health check grouping
  • index.html — self-contained dashboard with dark/light mode, sparklines, category tabs

Example

The example directory includes a demo WAR with three categorized health checks (Database, Messaging, Storage), custom config properties, and a Dockerfile for WildFly 39 deployment via Podman.

Combining Both Addons

The OpenAPI GUI Addon and the Dashboard Addon work well together. Add both dependencies and you get:

  • /openapi-ui/ — API documentation with multi-app dropdown
  • /dashboard — operational health, metrics, and config

Both share the same project-stage gating pattern and can be controlled independently via their respective config properties.

AI-Assisted Development

Like the OpenAPI GUI Addon and the Dynamic CDI Test Bean Addon, this project was developed with an AI co-pilot in a single session. The dashboard evolved from a basic health-and-config page through several iterations — adding categorized health checks, JVM metrics from the Prometheus endpoint, sparkline history charts, and the project-stage filter. The security hooks ensured safe builds throughout.

Links

Previous posts in this series: OpenAPI GUI Addon | Upgrading 30+ Projects to Java 25 | Hardening Claude Code | Dynamic CDI Test Bean Addon

Saturday, April 18, 2026

MicroProfile OpenAPI GUI Addon

This is the fourth post in a series about AI-assisted Jakarta EE development. After upgrading 30+ projects to Java 25, hardening the AI toolchain with security hooks, and building a CDI test extension from scratch, this time we tackle a practical gap in the MicroProfile ecosystem: a proper multi-app OpenAPI UI with project-stage support.

The Problem

If you've ever deployed multiple REST APIs on the same application server, you know the pain: each app needs its own OpenAPI UI, the server-level /openapi endpoint only serves one deployment, and the existing community addons like microprofile-extensions/openapi-ext don't support switching between APIs from a single UI.

On top of that, you probably want to disable the UI in production — but the existing addons auto-register via web-fragment.xml, giving you no control over when they're active.

The Solution: openapi-gui-addon

The openapi-gui-addon is a self-contained OpenAPI UI addon for Jakarta EE / MicroProfile applications that solves these problems. It builds on the microprofile-extensions openapi-ext but adds several key features:

  • Multi-API dropdown — configure multiple OpenAPI endpoints and switch between them from a single UI
  • Availability checking — on every page refresh, each configured endpoint is checked via a HEAD request. Reachable APIs appear as selectable, unreachable ones are shown as disabled below a separator
  • Project-stage gating — disable the UI in production via config, no code changes needed (Jakarta EE 11) or via Application.getClasses() (Jakarta EE 8)
  • Fully self-contained — Swagger UI bundled, no external requests
  • Configurable via MicroProfile Config — all settings in microprofile-config.properties

Quick Start

Add the dependency:

<dependency>
    <groupId>org.os890.mp-ext</groupId>
    <artifactId>openapi-gui-addon</artifactId>
    <version>1.11.0</version>
</dependency>

Configure the endpoint in META-INF/microprofile-config.properties:

openapi.ui.yamlUrl=/my-app/openapi
openapi.ui.title=My API
mp.openapi.extensions.path=/my-app/openapi

That's it — your OpenAPI UI is available at /my-app/openapi-ui/. No code needed beyond your REST resources.

Multi-API Dropdown

The real power shows when you deploy multiple APIs on the same server. Add the openapi.ui.urls property to configure the dropdown:

openapi.ui.urls=Order API=/order-api/openapi,Customer API=/customer-api/openapi,Inventory API=/inventory-api/openapi

The format is simple: comma-separated Name=URL pairs. The UI then shows a "Select a definition" dropdown in the top bar. On every page load, each URL is checked for availability:

  • Reachable APIs appear as normal selectable entries
  • Unreachable APIs (not deployed, wrong URL, server down) appear below a "── Unavailable ──" separator as disabled/greyed-out entries

This is useful when you have a shared config (e.g., via a config JAR) that lists all known APIs — the UI gracefully handles APIs that aren't currently deployed.

Project-Stage Gating

The addon includes a built-in @PreMatching JAX-RS filter that controls UI access based on two MicroProfile Config properties:

  • openapi.ui.enabled — explicit override (true/false). When set, this takes precedence.
  • project.stage — if set to production and openapi.ui.enabled is not explicitly set, the UI is disabled.

This means: no code changes needed for project-stage gating. Just configure via properties.

Runtime Config (JVM System Property)

Pass -Dproject.stage=development to the JVM to enable the UI. Without it, the default behavior is production (UI disabled).

Build-Time Maven Profile

The most secure approach: don't ship the addon JAR at all in production. Place the dependency in a Maven profile so it's only included for non-production builds:

<profiles>
    <profile>
        <id>development</id>
        <dependencies>
            <dependency>
                <groupId>org.os890.mp-ext</groupId>
                <artifactId>openapi-gui-addon</artifactId>
                <version>1.11.0</version>
            </dependency>
        </dependencies>
    </profile>
</profiles>

Build with mvn clean package -Pdevelopment to include the UI. The default build produces a WAR with zero addon code — nothing to exploit.

Always Enabled

For apps where the UI should always be available regardless of project stage, simply set:

openapi.ui.enabled=true

Jakarta EE 8 Alternative: getClasses()

On Jakarta EE 8 (version 1.8.0 of the addon), the auto-discovery filter is not available. Instead, control the UI via Application.getClasses():

import org.os890.mp.openapi.gui.OpenApiUiService;
import org.os890.mp.openapi.gui.StaticResourcesService;

@ApplicationPath("/")
public class MyApplication extends Application {

    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<>();
        classes.add(MyResource.class);

        String stage = ConfigProvider.getConfig()
                .getOptionalValue("project.stage", String.class)
                .orElse("production");

        if (!"production".equals(stage)) {
            classes.add(OpenApiUiService.class);
            classes.add(StaticResourcesService.class);
        }
        return classes;
    }
}

How It Works Under the Hood

The addon uses the Maven Shade Plugin to repackage microprofile-extensions/openapi-ext with package relocation from org.microprofileext.openapi.swaggerui to org.os890.mp.openapi.gui. It replaces only the files that were changed:

  • Templates.java — adds openapi.ui.urls config, availability-checking JavaScript, and auto-visible explore form when the dropdown is active
  • template.html — removes the Google Fonts CDN link and adds the client-side availability check with the multi-API dropdown logic
  • web-fragment.xml — removed entirely, enabling explicit registration control
  • OpenApiUiAccessFilter (new, Jakarta EE 11 only) — @PreMatching JAX-RS filter for config-driven project-stage gating

Everything else (OpenApiUiService, StaticResourcesService, RequestInfo, WhiteLabel, style.css, logo.png) comes unchanged from the original addon via the shade. The result is a single self-contained JAR with no transitive runtime dependencies.

Versions

Two versions are available:

VersionJakarta EENamespaceSwagger UIGatingTested with
1.8.08javax.*4.15.5getClasses()WildFly 25
1.11.011jakarta.*5.18.2Auto-filter + configWildFly 39

Examples

The examples directory contains three demo applications showing the different project-stage approaches, deployable on WildFly 39 via Podman. An interactive build_and_start.sh script lets you choose the project stage for each demo before building and deploying.

AI-Assisted Development

Like the Dynamic CDI Test Bean Addon, this project was developed with an AI co-pilot — from the initial Jakarta EE 8 demo through the shade-based addon architecture to the Jakarta EE 11 auto-discovery filter. The entire process, including the backport of the upstream library, the multi-API dropdown with availability checking, and the project-stage gating mechanism, was built iteratively in a single session. The security hooks we set up earlier ensured that all package installs and web requests stayed within safe boundaries throughout.

Links

Previous posts in this series: Upgrading 30+ Projects to Java 25 | Hardening Claude Code | Dynamic CDI Test Bean Addon

Friday, April 10, 2026

Upgrading 30+ Open-Source Projects to Java 25 — With an AI Co-Pilot

After launching the Dynamic CDI Test Bean Addon as the first fully AI-created os890 project, the natural next step was putting it to work: upgrading the entire os890 open-source portfolio to Java 25 and modern Jakarta EE.

The Starting Point

Over the years, the os890 GitHub account accumulated 30+ projects — CDI extensions, DeltaSpike add-ons, project templates, and demo applications. Most were stuck on Java 6–8, the javax.* namespace, DeltaSpike 1.x, and JUnit 4. Some hadn't seen a commit in years. They all worked, but they were falling behind the ecosystem.

The Upgrade Scope

Every project received the same treatment:

  • Java 25 with maven.compiler.release
  • Jakarta EE namespace migration (javax.*jakarta.*) across all Java sources, XML configs, service files, and persistence descriptors
  • DeltaSpike 2.x with adaptation to removed APIs (e.g., ExceptionToCatchEvent, DeltaSpikeLiteral)
  • JUnit Jupiter 6 replacing JUnit 4, with all existing assertions preserved
  • CDI 4.1 namespace in all beans.xml files
  • Test migration from Weld/DeltaSpike test-control to the new Dynamic CDI Test Bean Addon with @EnableTestBeans
  • Seven quality plugins: compiler warnings as errors, enforcer (dependency convergence + banned javax.*), checkstyle, Apache RAT license verification, pinned surefire, JaCoCo coverage, and javadoc
  • Apache License 2.0 — the official canonical text, verified via an enforcer checksum rule in every build

The Quality Bar

The upgrade wasn't just "make it compile on Java 25." Every project now enforces zero tolerance:

  • Zero checkstyle violations (including test sources)
  • Zero unapproved license headers (Apache RAT)
  • Zero compiler warnings (-Xlint:all with failOnWarning)
  • Zero dependency convergence conflicts
  • All javax.* dependencies banned via enforcer
  • LICENSE file integrity verified by MD5 checksum in every build

If any of these checks fail, the build fails. No exceptions, no suppressions.

Where the AI Co-Pilot Helped

An upgrade like this is conceptually simple but mechanically brutal. Across 30+ projects, you're touching hundreds of files — and every project has its own quirks: multi-module structures, profiles for different containers (TomEE, Weld, OWB), deprecated APIs that disappeared between library versions, and serialization warnings on CDI interceptors that implement Serializable through framework interfaces.

The agentic AI workflow handled the repetitive mechanical work — namespace replacements, dependency version bumps, quality plugin configuration, license headers, Javadoc — while I focused on the decisions that require judgment: whether a logic change is justified by an API removal, whether a @SuppressWarnings("serial") is acceptable on a CDI interceptor, or whether removed test profiles should be restored.

Compared to rule-based migration tools like OpenRewrite, the agentic AI approach was significantly more flexible. In my experience, tools like OpenRewrite tend to handle only the standard cases well — and sometimes only in simple, single-module project structures. Once you hit multi-module builds, container-specific Maven profiles, or APIs that were removed rather than just renamed, those tools hit their limits quickly and you're back to manual work anyway. The agentic AI approach, on the other hand, handled the full complexity: adapting to removed APIs, restructuring test infrastructure, resolving container-specific quirks across different profiles, and making judgment calls about code that couldn't be mechanically migrated. With an AI co-pilot, the conversation was iterative: explain the intent, review the result, course-correct where needed.

Quality was further improved by adding proper README files and Javadoc to every project. Each README now documents the project's purpose, usage, and build instructions — making the repositories approachable again after years of minimal documentation. The Javadoc pass ensured that public APIs are properly documented, and with the Javadoc Maven plugin configured to fail on errors, this standard is enforced in every build going forward.

Some concrete examples where human review caught issues the AI initially missed:

  • Tests that were silently removed instead of being upgraded in place
  • Logic was silently rewritten instead of being upgraded
  • Extra -Xlint suppression flags that violated the quality rules
  • Enforcer rules that banned specific javax artifacts instead of using a wildcard pattern
  • LICENSE files that looked like Apache 2.0 but were actually paraphrased variants

The Result

All projects now build cleanly on Java 25 with mvn clean verify — including all Maven profiles. Every project has comprehensive tests using the Dynamic CDI Test Bean Addon, proper license files, and a strict quality gate that prevents regressions.

The enforcer checksum rule for the LICENSE file is a small detail I'm particularly happy about. It's one line of XML that guarantees nobody accidentally replaces the canonical Apache 2.0 text with an abbreviated version — something that actually happened during the AI-assisted upgrade and was only caught during review.

Lessons Learned

  1. Codify your rules. Writing the upgrade rules in a Markdown file that both the human and the AI could reference eliminated ambiguity. When the AI made a choice that violated a rule, pointing to the specific rule made the correction instant.
  2. Verify, don't trust. Every upgrade went through: audit → fix → build all profiles → review → push. The AI is fast at the mechanical work but occasionally takes shortcuts (suppressing warnings instead of fixing them, removing code instead of migrating it).
  3. Checksums are underrated. The LICENSE file saga — where every repo had a different "Apache 2.0" text — would have been invisible without comparing MD5 hashes. The enforcer checksum rule now makes this a build-time guarantee.

What's Next

With all projects on a common modern baseline, the next focus is on the projects themselves — new features, better documentation, and exploring what CDI 4.1 makes possible that wasn't feasible before. The quality infrastructure is now solid enough that contributions (human or AI) are held to the same standard automatically.


Disclaimer: This blog post was generated after the upgrade process, based on the interactions and conversations that took place during the upgrades. The upgrades themselves were performed using an agentic AI workflow with human review and approval at every step. All code changes were verified by building and testing before being pushed.

Saturday, April 4, 2026

Hardening Claude Code: Security Hooks for Package Managers & Web Access

In the previous post I introduced the Dynamic CDI Test Bean Addon — the first os890 CDI extension created entirely in an agentic AI workflow. During that and other longer multi-day sessions for larger projects with Claude, I also needed to harden the AI agent itself. Here's what came out of that: a set of PreToolUse hooks that improve security guardrails at runtime — suggested and implemented by Claude itself.

Disclaimer — Use at Your Own Risk and without warranty These hooks are a best-effort hardening measure, not a security guarantee. They are not a replacement for a proper outbound firewall, network-level controls, or enterprise security policies. Hook behavior may change with future Claude Code updates. URL extraction uses regex and may miss obfuscated network calls. Provided as-is, without warranty and at your own risk. Test thoroughly before relying on them.

The Two Risks

Claude Code can run shell commands, install packages, and fetch web content on your behalf. Two things which needed attention when called without scripts:

1. Supply-chain attacksnpm install / pnpm add / yarn add / bun install can execute arbitrary preinstall/postinstall scripts. A single malicious dependency can steal credentials or install backdoors.

2. Unrestricted web access — Claude Code can fetch any URL via WebFetch, WebSearch, or curl/wget. Prompt injection in fetched content could trick it into exfiltrating data.

There is also a third, subtler motivation: keeping research focused. In longer agentic sessions, an unrestricted agent will readily pull in information from wherever it lands — forums, random blogs, outdated docs. Limiting web access to a curated allowlist of trusted sources means Claude's research stays grounded in high-quality references rather than drifting toward whatever the search engine surfaces.

CLAUDE.md instructions help, but they can be ignored. Hooks can't — they intercept tool calls before execution.

The Setup

Two Node.js scripts in ~/.claude/, wired via settings.json:

1. package-install-check.js — blocks any npm/pnpm/yarn/bun install command that doesn't include --ignore-scripts:

// Receives tool call JSON on stdin, outputs decision to stdout
const input = JSON.parse(data);
const cmd   = input.tool_input.command || '';

if (
  /\b(npm|pnpm|yarn|bun)\s+(install|i|ci|add)\b/.test(cmd) &&
  !/--ignore-scripts/.test(cmd)
) {
  // deny — block before execution
} else {
  // allow
}

2. domain-check.js — checks URLs against ~/.claude/allowed-domains.txt. Handles WebFetch (URL check), WebSearch (allowed_domains parameter check), and Bash (curl/wget URL extraction).

3. settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node ~/.claude/package-install-check.js"
          }
        ]
      },
      {
        "matcher": "WebFetch",
        "hooks": [
          {
            "type": "command",
            "command": "node ~/.claude/domain-check.js"
          }
        ]
      },
      {
        "matcher": "WebSearch",
        "hooks": [
          {
            "type": "command",
            "command": "node ~/.claude/domain-check.js"
          }
        ]
      }
    ]
  }
}

Lesson Learned: Hook Response Format

One thing that cost us time: the hook response format. Claude initially used {"decision":"allow"} which caused a PreToolUse:Bash hook error on every command. The correct format is:

// Allow
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow"
  }
}

// Block
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Explanation"
  }
}

Also: use file-based scripts with async stdin reading (process.stdin.on('data'/'end')), not inline node -e one-liners. The inline approach has stdin timing issues that cause spurious errors.

Full Setup & Scripts

The complete setup — including one-command install/uninstall scripts, the full domain-check.js, a starter domain whitelist, verification tests, and known limitations — will follow soon.

Dynamic CDI Test Bean Addon — The First os890 CDI Extension Created Entirely by AI

Welcome to os890.ai — a new blog dedicated to the open-source side of agentic AI workflows. If you've followed os890.blogspot.com for Java and CDI content, this is its AI-focused companion: a space to document what happens when agentic AI meets open-source tools, libraries, and real-world development workflows.

The first project featured here sets the tone perfectly.

The Dynamic CDI Test Bean Addon is a CDI portable extension that automatically mocks unsatisfied injection points using Mockito — enabling clean CDI SE tests in multi-module projects where not all beans are on the classpath. A useful open-source tool in its own right. But what makes it the right starting point for this blog is how it was built:

The entire addon was created from scratch using an agentic AI workflow — extension logic, 64 tests against both Weld and OpenWebBeans, build configuration with Enforcer, Checkstyle, and Apache RAT, DeltaSpike integration, and all accompanying documentation including this post.

No hand-written code. No manual scaffolding. Just an agentic workflow driving open-source tooling from zero to a fully tested, properly licensed, publishable library.

This project was deliberately chosen for that reason: a CDI extension is a small but precise piece of software. There is no room for vague approximations — contracts must be correct, edge cases in dependency resolution matter, and the test suite runs against two different CDI implementations. It is the kind of project where implementation details are not cosmetic. That makes it a much more honest proving ground for agentic AI than building a typical app where the rough edges of generated code rarely surface.

That intersection — agentic AI and open-source software, tested against problems that actually require precision — is exactly what this blog is about. How far can these workflows go? What does it take to produce production-quality open-source artifacts with AI? Where do the rough edges still show? These are the questions os890.ai will keep exploring.

Try It or Build On It

The project site covers the full feature set, from zero-config auto-mocking to composable @TestBean meta-annotations. It also ships with a SKILL.md file — a structured description designed to make the addon easy to pick up and use inside your own agentic AI workflows.

What's Next

This project also served as a real-world validation run for jawelte — more on that soon.

MicroProfile Dashboard Addon

Following up on the OpenAPI GUI Addon , this post introduces a companion addon for the operational side: a self-contained MicroProfile D...