Skip to content

Generation of JSON schema from Micronaut Configuration Properties#12377

Merged
graemerocher merged 6 commits into
5.0.xfrom
json-schema-config-properties
Feb 4, 2026
Merged

Generation of JSON schema from Micronaut Configuration Properties#12377
graemerocher merged 6 commits into
5.0.xfrom
json-schema-config-properties

Conversation

@graemerocher

@graemerocher graemerocher commented Feb 3, 2026

Copy link
Copy Markdown
Contributor

This PR adds generation of JSON schema definitions for @ConfigurationProperties and @EachPropery. The idea here is to use these definitions in build tooling (coming in a separate PR) to validate user configuration. AI was used in the generation of the implementation.

Example JSON schema for HttpServerConfiguration.:

{
  "$schema": "https:\/\/json-schema.org\/draft\/2020-12\/schema",
  "$id": "urn:micronaut:config:io.micronaut.http.server.HttpServerConfiguration",
  "title": "io.micronaut.http.server.HttpServerConfiguration",
  "description": "<p>A base {@link ConfigurationProperties} for servers.<\/p>",
  "x-micronaut": {
    "prefix": "micronaut.server",
    "type": "io.micronaut.http.server.HttpServerConfiguration",
    "kind": "configuration-properties"
  },
  "type": "object",
  "properties": {
    "http-version": {
      "type": "string",
      "enum": [
        "HTTP_1_0",
        "HTTP_1_1",
        "HTTP_2_0"
      ],
      "description": "Sets the HTTP version to use. Defaults to {@link HttpVersion#HTTP_1_1}.",
      "x-micronaut-javaType": "io.micronaut.http.HttpVersion",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.http-version"
    },
    "thread-selection": {
      "type": "string",
      "enum": [
        "AUTO",
        "MANUAL",
        "IO",
        "BLOCKING"
      ],
      "description": "Sets the {@link io.micronaut.scheduling.executor.ThreadSelection} model to use for the server. Default value MANUAL.",
      "x-micronaut-javaType": "io.micronaut.scheduling.executor.ThreadSelection",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.thread-selection"
    },
    "default-charset": {
      "type": "string",
      "description": "The default charset to use",
      "x-micronaut-javaType": "java.nio.charset.Charset",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.default-charset"
    },
    "port": {
      "type": "integer",
      "description": "Sets the port to bind to. Default value ({@value #DEFAULT_RANDOM_PORT})",
      "x-micronaut-javaType": "int",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.port",
      "default": 8080
    },
    "host": {
      "type": "string",
      "description": "Sets the host to bind to.",
      "x-micronaut-javaType": "java.lang.String",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.host"
    },
    "read-timeout": {
      "type": "integer",
      "description": "Sets the default read timeout.",
      "x-micronaut-javaType": "java.lang.Integer",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.read-timeout"
    },
    "max-request-size": {
      "type": "integer",
      "description": "Sets the maximum request size. Default value ({@value #DEFAULT_MAX_REQUEST_SIZE} =&gt; \/\/ 10MB)",
      "x-micronaut-javaType": "long",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.max-request-size",
      "default": 10485760
    },
    "max-request-buffer-size": {
      "type": "integer",
      "description": "Sets the maximum number of request bytes that will be buffered. Fully streamed requests can\n still exceed this value. Default value ({@value #DEFAULT_MAX_REQUEST_BUFFER_SIZE} =&gt; \/\/ 10MB).\n Currently limited to {@code 2^31}, if you need longer request bodies, stream them.<br>\n Note that there is always some internal buffering, so a very low value ({@code < ~64K}) will\n essentially act like a request size limit.",
      "x-micronaut-javaType": "long",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.max-request-buffer-size",
      "default": 10485760
    },
    "read-idle-timeout": {
      "type": "string",
      "format": "duration",
      "description": "Sets the amount of time a connection can remain idle without any reads occurring. Default value ({@value #DEFAULT_READ_IDLE_TIME_MINUTES} minutes).",
      "x-micronaut-javaType": "java.time.Duration",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.read-idle-timeout"
    },
    "write-idle-timeout": {
      "type": "string",
      "format": "duration",
      "description": "Sets the amount of time a connection can remain idle without any writes occurring. Default value ({@value #DEFAULT_WRITE_IDLE_TIME_MINUTES} minutes).",
      "x-micronaut-javaType": "java.time.Duration",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.write-idle-timeout"
    },
    "idle-timeout": {
      "type": "string",
      "format": "duration",
      "description": "Sets the idle time of connections for the server. Default value ({@value #DEFAULT_IDLE_TIME_MINUTES} minutes).",
      "x-micronaut-javaType": "java.time.Duration",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.idle-timeout"
    },
    "server-header": {
      "type": "string",
      "description": "Sets the name of the server header.",
      "x-micronaut-javaType": "java.lang.String",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.server-header"
    },
    "date-header": {
      "type": "boolean",
      "description": "Sets whether a date header should be sent back. Default value ({@value #DEFAULT_DATEHEADER}).",
      "x-micronaut-javaType": "boolean",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.date-header"
    },
    "log-handled-exceptions": {
      "type": "boolean",
      "description": "Sets whether exceptions handled by either an error route or exception handler\n should still be logged. Default value ({@value #DEFAULT_LOG_HANDLED_EXCEPTIONS }).",
      "x-micronaut-javaType": "boolean",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.log-handled-exceptions",
      "default": false
    },
    "client-address-header": {
      "type": "string",
      "description": "Which header stores the original client",
      "x-micronaut-javaType": "java.lang.String",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.client-address-header"
    },
    "context-path": {
      "type": "string",
      "description": "Sets the context path for the web server.",
      "x-micronaut-javaType": "java.lang.String",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.context-path"
    },
    "dual-protocol": {
      "type": "boolean",
      "description": "if dual protocol has been enabled or not",
      "x-micronaut-javaType": "boolean",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.dual-protocol",
      "default": false
    },
    "http-to-https-redirect": {
      "type": "boolean",
      "description": "if redirection from HTTP to HTTPS is enabled or not",
      "x-micronaut-javaType": "boolean",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.http-to-https-redirect",
      "default": false
    },
    "dispatch-options-requests": {
      "type": "boolean",
      "description": "Set to true to dispatch OPTIONS requests. Default value ({@value #DEFAULT_DISPATCH_OPTIONS_REQUESTS}.",
      "x-micronaut-javaType": "boolean",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.dispatch-options-requests",
      "default": false
    },
    "validate-url": {
      "type": "boolean",
      "description": "If the url should be validated by converting it to {@link java.net.URI}.",
      "x-micronaut-javaType": "boolean",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.validate-url"
    },
    "escape-html-url": {
      "type": "boolean",
      "description": "Browsers can send characters (such as {@code |}) which are not permitted under RFC 3986 as\n part of the request path. These characters are normally rejected by the server. If this\n setting is enabled, the server will escape these characters before parsing them using\n {@link java.net.URI} so that they are not rejected. Default off.",
      "x-micronaut-javaType": "boolean",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.escape-html-url"
    },
    "not-found-on-missing-body": {
      "type": "boolean",
      "description": "True if not-found should be returned on missing body. False to return an empty body.",
      "x-micronaut-javaType": "boolean",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.not-found-on-missing-body"
    },
    "semicolon-is-normal-char": {
      "type": "boolean",
      "description": "Sets whether the semicolon should be considered a normal character in the query.\n A \"normal\" semicolon is one that is not used as a parameter separator.",
      "x-micronaut-javaType": "boolean",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.semicolon-is-normal-char",
      "default": false
    },
    "max-params": {
      "type": "integer",
      "description": "the maximum parameter count.",
      "x-micronaut-javaType": "int",
      "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration",
      "x-micronaut-path": "micronaut.server.max-params",
      "default": 1024
    },
    "multipart": {
      "type": "object",
      "properties": {
        "location": {
          "type": "string",
          "description": "Sets the location to store files.",
          "x-micronaut-javaType": "java.io.File",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$MultipartConfiguration",
          "x-micronaut-path": "micronaut.server.multipart.location"
        },
        "max-file-size": {
          "type": "integer",
          "description": "Sets the max file size. Default value ({@value #DEFAULT_MAX_FILE_SIZE} =&gt; 1MB).",
          "x-micronaut-javaType": "long",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$MultipartConfiguration",
          "x-micronaut-path": "micronaut.server.multipart.max-file-size"
        },
        "enabled": {
          "type": "boolean",
          "description": "Sets whether multipart processing is enabled. Default value ({@value #DEFAULT_ENABLED}).",
          "x-micronaut-javaType": "boolean",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$MultipartConfiguration",
          "x-micronaut-path": "micronaut.server.multipart.enabled"
        },
        "disk": {
          "type": "boolean",
          "description": "Sets whether to buffer data to disk or not. Default value ({@value #DEFAULT_DISK}).",
          "x-micronaut-javaType": "boolean",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$MultipartConfiguration",
          "x-micronaut-path": "micronaut.server.multipart.disk"
        },
        "mixed": {
          "type": "boolean",
          "description": "Sets whether to buffer data to disk if the size is greater than the\n threshold. Default value ({@value #DEFAULT_MIXED}).",
          "x-micronaut-javaType": "boolean",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$MultipartConfiguration",
          "x-micronaut-path": "micronaut.server.multipart.mixed"
        },
        "threshold": {
          "type": "integer",
          "description": "Sets the amount of data that should be received that will trigger\n the data to be stored to disk. Default value ({@value #DEFAULT_THRESHOLD}).",
          "x-micronaut-javaType": "long",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$MultipartConfiguration",
          "x-micronaut-path": "micronaut.server.multipart.threshold"
        }
      }
    },
    "cors": {
      "type": "object",
      "properties": {
        "enabled": {
          "type": "boolean",
          "description": "Sets whether CORS is enabled. Default value ({@value #DEFAULT_ENABLED})",
          "x-micronaut-javaType": "boolean",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$CorsConfiguration",
          "x-micronaut-path": "micronaut.server.cors.enabled"
        },
        "localhost-pass-through": {
          "type": "boolean",
          "description": "Sets whether localhost pass-through is enabled. Default value {@value #DEFAULT_LOCALHOST_PASS_THROUGH}.\n Setting this to true will allow requests to be made to localhost from any origin.",
          "x-micronaut-javaType": "boolean",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$CorsConfiguration",
          "x-micronaut-path": "micronaut.server.cors.localhost-pass-through"
        },
        "configurations": {
          "type": "object",
          "additionalProperties": {
            "type": "string"
          },
          "description": "Sets the CORS configurations.",
          "x-micronaut-javaType": "java.util.Map",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$CorsConfiguration",
          "x-micronaut-path": "micronaut.server.cors.configurations"
        },
        "single-header": {
          "type": "boolean",
          "description": "Sets whether CORS header values should be joined into a single header. Default value ({@value #DEFAULT_SINGLE_HEADER}).",
          "x-micronaut-javaType": "boolean",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$CorsConfiguration",
          "x-micronaut-path": "micronaut.server.cors.single-header"
        }
      }
    },
    "host-resolution": {
      "type": "object",
      "properties": {
        "host-header": {
          "type": "string",
          "description": "The host header name",
          "x-micronaut-javaType": "java.lang.String",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$HostResolutionConfiguration",
          "x-micronaut-path": "micronaut.server.host-resolution.host-header"
        },
        "protocol-header": {
          "type": "string",
          "description": "The protocol header name",
          "x-micronaut-javaType": "java.lang.String",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$HostResolutionConfiguration",
          "x-micronaut-path": "micronaut.server.host-resolution.protocol-header"
        },
        "port-header": {
          "type": "string",
          "description": "The port header name",
          "x-micronaut-javaType": "java.lang.String",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$HostResolutionConfiguration",
          "x-micronaut-path": "micronaut.server.host-resolution.port-header"
        },
        "port-in-host": {
          "type": "boolean",
          "description": "If the host header supports a port",
          "x-micronaut-javaType": "boolean",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$HostResolutionConfiguration",
          "x-micronaut-path": "micronaut.server.host-resolution.port-in-host"
        },
        "allowed-hosts": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "The list of hosts to validate the resolved host against.",
          "x-micronaut-javaType": "java.util.List",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$HostResolutionConfiguration",
          "x-micronaut-path": "micronaut.server.host-resolution.allowed-hosts"
        }
      }
    },
    "locale-resolution": {
      "type": "object",
      "properties": {
        "fixed": {
          "type": "string",
          "description": "Set the language tag for the locale. Supports BCP 47 language\n tags (e.g. \"en-US\") and ISO standard (e.g \"en_US\").",
          "x-micronaut-javaType": "java.util.Locale",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$HttpLocaleResolutionConfigurationProperties",
          "x-micronaut-path": "micronaut.server.locale-resolution.fixed"
        },
        "session-attribute": {
          "type": "string",
          "description": "Sets the key in the session to look for the locale.",
          "x-micronaut-javaType": "java.lang.String",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$HttpLocaleResolutionConfigurationProperties",
          "x-micronaut-path": "micronaut.server.locale-resolution.session-attribute"
        },
        "default-locale": {
          "type": "string",
          "description": "Sets the locale that will be used if the locale cannot be\n resolved through any means. Defaults to the system default.",
          "x-micronaut-javaType": "java.util.Locale",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$HttpLocaleResolutionConfigurationProperties",
          "x-micronaut-path": "micronaut.server.locale-resolution.default-locale"
        },
        "cookie-name": {
          "type": "string",
          "description": "Sets the name of the cookie that is used to store the locale.",
          "x-micronaut-javaType": "java.lang.String",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$HttpLocaleResolutionConfigurationProperties",
          "x-micronaut-path": "micronaut.server.locale-resolution.cookie-name"
        },
        "header": {
          "type": "boolean",
          "description": "Set to true if the locale should be resolved from the `Accept-Language` header.\n Default value ({@value #DEFAULT_HEADER_RESOLUTION}).",
          "x-micronaut-javaType": "boolean",
          "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$HttpLocaleResolutionConfigurationProperties",
          "x-micronaut-path": "micronaut.server.locale-resolution.header"
        }
      }
    },
    "responses": {
      "type": "object",
      "properties": {
        "file": {
          "type": "object",
          "properties": {
            "cache-seconds": {
              "type": "integer",
              "description": "Cache Seconds. Default value ({@value #DEFAULT_CACHESECONDS}).",
              "x-micronaut-javaType": "int",
              "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$FileTypeHandlerConfiguration",
              "x-micronaut-path": "micronaut.server.responses.file.cache-seconds"
            },
            "cache-control": {
              "type": "object",
              "properties": {
                "public": {
                  "type": "boolean",
                  "description": "Sets whether the cache control is public. Default value ({@value #DEFAULT_PUBLIC_CACHE})",
                  "x-micronaut-javaType": "boolean",
                  "x-micronaut-sourceType": "io.micronaut.http.server.HttpServerConfiguration$FileTypeHandlerConfiguration$CacheControlConfiguration",
                  "x-micronaut-path": "micronaut.server.responses.file.cache-control.public"
                }
              }
            }
          }
        }
      }
    }
  }
}

@github-project-automation github-project-automation Bot moved this to Backlog in 5.0.0-M2 Feb 3, 2026
@graemerocher graemerocher added the type: enhancement New feature or request label Feb 3, 2026
@graemerocher graemerocher requested a review from melix February 4, 2026 08:14

@melix melix left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct even though I don't know enough of JSON Schema to tell if the generated format is ok. Comments are mostly around code style: the code doesn't look modern Java (no var, instanceof checks, final local variables, ...) and typical FQCN leftovers.

return parts;
}

private void refEntry(Writer out) throws IOException {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICS helper methods could be static, or, maybe better, have a wrapper around Writer so that we don't have to pass out on every call.

@graemerocher graemerocher force-pushed the json-schema-config-properties branch from 919e828 to 552c038 Compare February 4, 2026 11:36
@graemerocher graemerocher force-pushed the json-schema-config-properties branch from 8919fb7 to 28e2831 Compare February 4, 2026 11:57
@sonarqubecloud

sonarqubecloud Bot commented Feb 4, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
10.3% Duplication on New Code (required ≤ 10%)
10 New Critical Issues (required ≤ 0)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@graemerocher graemerocher merged commit a3f9cb9 into 5.0.x Feb 4, 2026
9 of 11 checks passed
@graemerocher graemerocher deleted the json-schema-config-properties branch February 4, 2026 14:12
@github-project-automation github-project-automation Bot moved this from Backlog to Done in 5.0.0-M2 Feb 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: enhancement New feature or request

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants