Skip to content

Replace config-properties.yml with standard spring-configuration-metadata.json#15426

Merged
jamesfredley merged 22 commits into7.0.xfrom
docs/configuration-hybrid
Apr 24, 2026
Merged

Replace config-properties.yml with standard spring-configuration-metadata.json#15426
jamesfredley merged 22 commits into7.0.xfrom
docs/configuration-hybrid

Conversation

@jamesfredley
Copy link
Copy Markdown
Contributor

@jamesfredley jamesfredley commented Feb 21, 2026

Summary

Replaces the custom config-properties.yml metadata with Spring Boot's standard spring-configuration-metadata.json format, distributed across the 9 modules that own each set of properties. This gives IDEs (IntelliJ, VS Code, Eclipse STS) auto-completion and hover documentation for Grails configuration properties out of the box.

The generated Application Properties.adoc reference recovers all content from the previous hand-written doc (descriptions, defaults, sections) while making it maintainable - each module owns its own metadata JSON and the doc is generated at build time. Compared to the old 135-property hand-written doc, the generated doc now covers 206 properties across 24 sections.

For Grails 8, a follow-up initiative (#15469) proposes migrating to @ConfigurationProperties annotated classes with the Spring Boot annotation processor, which would auto-generate the metadata JSON at compile time and eliminate manual maintenance entirely.

What Changed

New spring-configuration-metadata.json Files (9 modules)

Module Properties Categories
grails-core 45 Core, Events, JSON & Converters, Internationalization, GORM, DataSource
grails-web-core 33 Web & Controllers, CORS, MIME Types, Static Resources, URL Mappings, Scaffolding
grails-gsp 13 Views & GSP
grails-databinding 5 Data Binding
grails-data-hibernate5/core 3 Hibernate
grails-cache 6 Cache Plugin
grails-data-hibernate5/dbmigration 15 Database Migration Plugin
grails-data-mongodb/grails-plugin 23 MongoDB GORM Plugin (connections, pools, SSL)
grails-views-gson 9 JSON Views

Total: 152 properties across 9 modules, generating a 866-line reference doc with 24 sections.

Refactored ConfigReportCommand

  • Reads META-INF/spring-configuration-metadata.json from the classpath via ClassLoader.getResources() instead of custom YAML
  • Uses ConfigPropertyMetadata and MetadataResult immutable data classes instead of raw maps
  • Separates environment-derived properties (from System.getenv()) into their own report section
  • Groups unknown runtime properties into an "Other Properties" section
  • Removed SnakeYAML dependency for metadata parsing

Generated Documentation

  • generateConfigReference Gradle task in grails-doc/build.gradle reads metadata JSON from all 9 module JARs and generates Application Properties.adoc during publishGuide
  • Generated doc includes 24 sections with descriptions and defaults preserved from the original hand-written doc
  • Environment-specific configuration section at the top with YAML and Groovy examples, including list of default environments (development, test, production)
  • "Plugin References" section at the bottom linking directly to config property anchors in external plugin docs

Removed

  • grails-core/src/main/resources/META-INF/grails/config-properties.yml (814 lines of custom format)
  • grails-doc/src/en/ref/Configuration/Application Properties.adoc (1440 lines of checked-in content, now generated)

Companion PRs for External Plugins

Properties for plugins with their own repositories now have spring-configuration-metadata.json in their respective repos, each with a generated properties reference doc:

The "Plugin References" section in the generated doc links directly to the configuration properties section in each plugin's published documentation:

Future Direction

For Grails 8, issue #15469 tracks migrating to @ConfigurationProperties annotated classes with the spring-boot-configuration-processor. Five modules already have @ConfigurationProperties classes (DataBindingConfigurationProperties, GrailsCorsConfiguration, CachePluginConfiguration, JsonViewConfiguration, MarkupViewConfiguration) and could adopt auto-generation immediately. Modules using Groovy DSL config (like Spring Security's DefaultSecurityConfig.groovy) would need a phased migration.

Testing

  • All 17 ConfigReportCommandSpec tests pass
  • generateConfigReference Gradle task verified locally (866-line doc, 24 sections)
  • All 9 JSON files validated
  • CodeNarc clean
  • Compared against old hand-written doc: zero real properties lost (13 external plugin properties are now linked, 71 new properties added)

jamesfredley and others added 3 commits February 20, 2026 18:32
Add a comprehensive Application Properties reference page under
ref/Configuration/ listing all Grails-specific configuration keys
with descriptions and default values. Covers core framework, GORM,
DataSource, Hibernate, Database Migration Plugin, Cache Plugin,
Asset Pipeline Plugin, Spring Security Plugin, and MongoDB GORM Plugin.

All external links use doc variables ({springBootVersion},
{hibernate5Reference}) for version-safe URLs.

Assisted-by: OpenCode <opencode@opencode.ai>
Assisted-by: Claude <claude@anthropic.com>
Merge curated property metadata from the Application Properties
documentation with runtime-collected values from the Spring Environment.

The command now outputs a 3-column AsciiDoc table (Property, Description,
Default) organized by functional category (Core Properties, Web &
Controllers, CORS, GORM, etc.). Runtime values override static defaults
for known properties. Properties not found in the metadata appear in a
separate Other Properties section.

- Add config-properties.yml with ~160 properties across 21 categories
- Modify ConfigReportCommand to load YAML metadata and merge with runtime
- Update unit tests for 3-column hybrid format
- Update integration tests for hybrid category-based layout

Assisted-by: Claude Code <Claude@Claude.ai>
@jamesfredley
Copy link
Copy Markdown
Contributor Author

Add Apache license headers to config-properties.yml and logback.xml
that were missing required ASF headers, causing CI RAT audit failure.

Assisted-by: Claude Code <Claude@Claude.ai>
@jamesfredley jamesfredley self-assigned this Feb 26, 2026
@jamesfredley jamesfredley moved this to In Progress in Apache Grails Feb 26, 2026
@jamesfredley jamesfredley added this to the grails:7.0.8 milestone Feb 26, 2026
@jamesfredley jamesfredley marked this pull request as ready for review February 26, 2026 00:53
Copilot AI review requested due to automatic review settings February 26, 2026 00:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a hybrid ConfigReportCommand to Grails that merges curated configuration-property metadata (from a new classpath YAML file) with runtime-resolved values from Spring’s ConfigurableEnvironment, and includes a new grails-test-examples/config-report sample app to validate the output format end-to-end.

Changes:

  • Introduces ConfigReportCommand to generate a categorized AsciiDoc report using static metadata + runtime overrides, with an “Other Properties” section for non-metadata keys.
  • Adds META-INF/grails/config-properties.yml containing categorized property metadata (descriptions + defaults).
  • Adds/updates unit + integration tests, including a new grails-test-examples/config-report module wired into settings.gradle.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
settings.gradle Adds the new grails-test-examples-config-report included build project.
grails-core/src/main/groovy/grails/dev/commands/ConfigReportCommand.groovy Implements the hybrid config report generator (runtime collection + metadata merge + AsciiDoc output).
grails-core/src/main/resources/META-INF/grails/config-properties.yml Supplies categorized metadata used to render known properties.
grails-core/src/test/groovy/grails/dev/commands/ConfigReportCommandSpec.groovy Unit tests for property collection, metadata loading, escaping, and report layout.
grails-test-examples/config-report/** New sample application + integration spec validating runtime config capture and report structure.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread grails-core/src/main/groovy/grails/dev/commands/ConfigReportCommand.groovy Outdated
Comment thread grails-core/src/main/resources/META-INF/grails/config-properties.yml Outdated
Comment thread grails-core/src/main/resources/META-INF/grails/config-properties.yml Outdated
Add instanceof Map guard after Yaml.load() in loadPropertyMetadata() to
prevent NPE when the YAML resource is empty or contains a non-Map root.
Remove unused @tempdir field and its import from the integration test.

Assisted-by: Claude Code <Claude@Claude.ai>
@jamesfredley
Copy link
Copy Markdown
Contributor Author

@jdaugherty As long as CI passes, I think this is ready to review as the hybrid approach to generate the Configuration Reference.

…ies doc

Document how config properties are global by default and can be
overridden per environment. Include default environments table
(development/dev, test/test, production/prod), custom environment
support, and activation via -Dgrails.env / GRAILS_ENV.

Assisted-by: Claude Code <Claude@Claude.ai>
@jamesfredley
Copy link
Copy Markdown
Contributor Author

Updated screenshot with environments section: The-Grails-Framework-7-0-8-SNAPSHOT-02-26-2026_10_37_AM.pdf

Comment thread grails-core/src/main/groovy/grails/dev/commands/ConfigReportCommand.groovy Outdated
Comment thread grails-core/src/main/resources/META-INF/grails/config-properties.yml Outdated
Comment thread grails-core/src/main/resources/META-INF/grails/config-properties.yml Outdated
Replace custom YAML metadata with Spring configuration metadata, refactor config reporting to parse JSON resources, and generate docs during guide builds.

Assisted-by: OpenCode <opencode@opencode.ai>

Assisted-by: Claude Code <Claude@Claude.ai>
@jamesfredley jamesfredley changed the title feat: hybrid ConfigReportCommand with static metadata and runtime values Replace config-properties.yml with standard spring-configuration-metadata.json Feb 26, 2026
Use the correct Gradle project path :grails-data-hibernate5-core instead
of :grails-data-hibernate5:core which does not exist as a project.

Assisted-by: Claude Code <Claude@Claude.ai>
Remove java.util.Enumeration, java.util.Locale, and java.net.URL imports
that are auto-imported in Groovy and cause UnnecessaryGroovyImport
CodeNarc violations.

Assisted-by: Claude Code <Claude@Claude.ai>
@jamesfredley
Copy link
Copy Markdown
Contributor Author

jamesfredley commented Feb 26, 2026

Follow-up: Configuration Metadata for Other Plugins

This PR standardizes grails-core on spring-configuration-metadata.json. The following companion PRs add the same standard to ecosystem plugins:

Each plugin can follow the same pattern: add a META-INF/spring-configuration-metadata.json with groups/properties, then add a Gradle task to generate an AsciiDoc properties reference table for their docs. The generateConfigReference task in grails-doc/build.gradle will automatically pick up any new JSON files from plugin JARs on the classpath.

…d JSON Views

Add spring-configuration-metadata.json files for in-repo plugin modules:
- grails-cache: 6 properties (grails.cache.*)
- grails-data-hibernate5-dbmigration: 15 properties (grails.plugin.databasemigration.*)
- grails-data-mongodb: 23 properties (grails.mongodb.*)
- grails-views-gson: 9 properties (grails.views.json.*)

Update grails-gsp metadata descriptions to match original documentation.

Update generateConfigReference Gradle task to include all new modules
and fix Plugin References with direct anchor links to config sections.

Fix consecutive blank lines CodeNarc violation in ConfigReportCommand.

Assisted-by: Claude Code <Claude@Claude.ai>
- Move spring-configuration-metadata.json from grails-gsp/src/ to
  grails-gsp/plugin/src/ to match the :grails-gsp project directory
  (grails-gsp/plugin). This was preventing 13 Views & GSP properties
  from appearing in the generated doc.
- Update Spring Security link to correct published docs path
- Update Asset Pipeline link to GitHub Pages URL

Assisted-by: Claude Code <Claude@Claude.ai>
@testlens-app

This comment has been minimized.

@testlens-app

This comment has been minimized.

@testlens-app

This comment has been minimized.

@testlens-app
Copy link
Copy Markdown

testlens-app Bot commented Mar 31, 2026

✅ All tests passed ✅

🏷️ Commit: 9a6dd39
▶️ Tests: 20243 executed
⚪️ Checks: 30/30 completed


Learn more about TestLens at testlens.app.

Copy link
Copy Markdown
Contributor

@matrei matrei left a comment

Choose a reason for hiding this comment

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

Perhaps we should make a functional test app with tests that verify the defaults?

"name": "grails.urlmapping.cache.maxsize",
"type": "java.lang.Integer",
"description": "The maximum size of the URL mapping cache.",
"defaultValue": 1000
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.

I think this is actually 5000 (DefaultUrlMappingsHolder). But in generated application.yml files it is set to 1000.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@jdaugherty I think this came from the desire to pull them from a generated app vs the default in the code. Similar for comments below. Should it be default in code or in generated app, I lean towards code.

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.

We should change the default in the code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Resolved in e768e20: changed DEFAULT_MAX_WEIGHTED_CAPACITY in DefaultUrlMappingsHolder from 5000 to 1000 so the code default now matches the documented value (and what the generated application.yml used to contain before #15574 removed it). The JSON metadata stays at 1000 and now reflects the true code default.

"name": "grails.views.default.codec",
"type": "java.lang.String",
"description": "The default encoding codec for GSP output. Set to `html` to reduce XSS risk. Options: `none`, `html`, `base64`.",
"defaultValue": "none"
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.

I think, this property is only used as fallback default value of grails.views.gsp.codecs if that is not set, and in that case defaults to html. (GroovyPageParser, OutputEncodingSettings)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Resolved in e768e20: changed defaultValue from none to html and updated the description to clarify that this is the fallback default used for grails.views.gsp.codecs.expression / scriptlet / taglib / staticparts when those are not set.

"name": "grails.views.gsp.htmlcodec",
"type": "java.lang.String",
"description": "The HTML codec for GSP output (xml or html).",
"defaultValue": "xml"
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.

I think this has no default value, but if specified and the value does not start with xml or equal (ignoring case) xhtml, it uses legacy HTML4Encoder instead of HTMLEncoder (HTMLCodec)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Resolved in e768e20: removed the misleading xml default and rewrote the description to match the actual behavior - when unset (or when the value does not start with xml or equal xhtml ignoring case), a legacy HTML4-compatible encoder is used; setting it to xml or xhtml selects the XML-safe encoder.

"name": "grails.mime.disable.accept.header.userAgents",
"type": "java.util.List<java.lang.String>",
"description": "List of user agent substrings (e.g., Gecko, WebKit) for which Accept header processing is disabled.",
"defaultValue": []
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.

I think default value is: ['Gecko', 'WebKit', 'Presto', 'Trident'] (HttpServletResponseExtension)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Resolved in e768e20: changed defaultValue from [] to ["Gecko", "WebKit", "Presto", "Trident"] to match the regex pattern baked into HttpServletResponseExtension.useDefaultConfig(). Also tweaked the description to note the match is case-insensitive.

@jdaugherty
Copy link
Copy Markdown
Contributor

@jamesfredley I approved #15574 , once it's merged can you please update this & adjust for the feedback that @matrei has provided? We can then approve.

… urlmapping cache size

Addresses review feedback from matrei on #15426, reflecting the state after PR #15574 removed redundant defaults from generated application.yml files.

- grails.urlmapping.cache.maxsize: change DEFAULT_MAX_WEIGHTED_CAPACITY in DefaultUrlMappingsHolder from 5000 to 1000 so the code default matches the documented value (the generated application.yml value removed in #15574).

- grails.views.default.codec: correct defaultValue from 'none' to 'html' and clarify it is the fallback for the grails.views.gsp.codecs.* properties.

- grails.views.gsp.htmlcodec: remove misleading 'xml' default and describe the actual behavior (when unset, the legacy HTML4-compatible encoder is used; setting it to 'xml' or 'xhtml' selects the XML-safe encoder).

- grails.mime.disable.accept.header.userAgents: correct defaultValue from [] to ['Gecko', 'WebKit', 'Presto', 'Trident'] to match the regex baked into HttpServletResponseExtension.

Assisted-by: sisyphus:claude-opus-4-7
@jamesfredley jamesfredley merged commit bf766ca into 7.0.x Apr 24, 2026
32 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Apache Grails Apr 24, 2026
@jamesfredley jamesfredley deleted the docs/configuration-hybrid branch April 24, 2026 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants