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

No comments:

Post a Comment

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...