Grimm implements MicroProfile OpenAPI 4.2, which in turn rides on OpenAPI 3.1.0. This page lays out the vocabulary — Components, Paths, Operations, Schemas — and the mechanics of merging the various model sources.

The OpenAPI 3.1 model

The OpenAPI format describes an HTTP service through a structured JSON or YAML tree. Three top-level groups:

Level Role

info

Contract metadata: title, version, license, contact, description.

paths

Operation catalogue. Each key is a path (/pets/{id}), each value groups HTTP verbs (get, post, …) and their Operation.

components

Reuse catalogue: schemas, parameters, responses, requestBodies, headers, securitySchemes, links, callbacks, examples.

The MP OpenAPI 4.2 spec’s Java model mirrors that structure: the OpenAPI interface → Info, Paths, Components. Grimm provides concrete implementations (records or final classes) in io.vidocq.grimm.internal.model, produced by its own OASFactoryResolver.

Three sources, one merge

The "Processing rules" of MP OpenAPI 4.2 prescribe three model sources, merged in this order (lowest to highest priority):

  1. OASModelReader — an application class implementing org.eclipse.microprofile.openapi.OASModelReader, selected via mp.openapi.model.reader. Lets you build the OpenAPI programmatically; it is the starting model.

  2. Static file — META-INF/openapi.yaml, .yml or .json packaged in the classpath. Loaded by StaticFileReader. Acts as the "editorial skeleton" supplied by the design team.

  3. Annotation scan — visits every application class discovered by the CDI container: @Path resources, but also standalone carriers of @OpenAPIDefinition, @Server, @Tag, @SecurityScheme or @Schema. Driven by AnnotationScanner.

ModelMerger combines the three with the spec priority annotations > static file > reader: the static file overrides the conflicting elements of the reader model, and the annotations override any conflicting element of both. The result is a single OpenAPI, dropped into GrimmModelCache.

NEW In 0.3.0 and earlier, Grimm merged the static file first and the reader on top of it, so on a conflict the reader won over the static file. It now follows the spec order above: the static file wins over the reader.

How the sources combine NEW

The sources are applied in the order above, each one on top of the result of the previous ones: reader, then static file, then annotations, and finally the OASFilter if one is configured. Two rules decide what happens when several sources describe the same element:

  • A named component schema is taken whole. When two sources define components/schemas/Pet, the definition of the higher-priority source replaces the other one entirely. Properties of the lower-priority Pet that the winner does not declare are not kept.

  • Schemas at the same position are merged property by property. This covers the schema of a media type, the entries of a schema’s properties, and its items. Every keyword the higher-priority source sets wins; keywords it does not set are kept from the lower-priority source. Extensions (x-*) are merged the same way. Explicit empty values from the higher-priority source are kept (default: [], const: {}, empty extensions); only an empty type, enum, required or properties does not erase what a lower-priority source says. A $ref read from a static file is copied verbatim, with no short-name expansion.

OpenAPI 3.1 lets a Reference Object carry summary and description next to $ref. The MicroProfile OpenAPI 4.2 model has no summary property on most objects that can be a reference (parameter, request body, response, header, link, security scheme, callback), so a summary written next to a $ref in a static file is dropped there; a description lands on the object’s own description. A callback reads every key other than $ref and x-* as a callback expression, so it keeps neither. This is a limitation of the MicroProfile OpenAPI API, not something Grimm can fix on its own.

OASFilter filters

An application class implementing org.eclipse.microprofile.openapi.OASFilter can transform the document after merge, on an operation-by-operation basis. Activation via mp.openapi.filter=fqn.MyFilter. Grimm runs the filter in FilterInvoker, with a single top-down pass that dispatches on Operation, PathItem, APIResponse, Schema, Parameter and others.

The filter runs once, at document build time, not per request. It therefore has no access to HttpHeaders or to a security context — for per-call transforms, use a JAX-RS ContainerResponseFilter.

Configuration via MicroProfile Config

MP OpenAPI 4.2 §4 defines a set of mp.openapi.* keys read through MicroProfile Config — supplied by Ravel in the Vidocq ecosystem.

Key Effect

mp.openapi.scan.disable

Disables annotation scanning altogether.

mp.openapi.scan.packages

Whitelist of packages to scan (comma-separated).

mp.openapi.scan.classes

Whitelist of specific classes.

mp.openapi.scan.exclude.packages

Packages excluded from the scan.

mp.openapi.scan.exclude.classes

Classes excluded.

mp.openapi.model.reader

FQCN of the OASModelReader.

mp.openapi.filter

FQCN of the OASFilter.

mp.openapi.servers

List of server URLs to enforce on the document.

mp.openapi.schema.<FQCN>

JSON snippet defining a schema for a given class (override).

See Reference for the exhaustive list and the mp.openapi.servers.path.<path> / mp.openapi.servers.operation.<operationId> variants.

Differences from OpenAPI 3.0 (reminder)

OpenAPI 3.1 (served by MP OpenAPI 4.2) fully aligns with JSON Schema 2020-12: type may be an array (["string", "null"]), nullable is replaced by that syntax, examples is an array, webhooks is added at the root. Grimm reflects these choices in its serializer.

Further reading