jamesfredley opened a new issue, #16122:
URL: https://github.com/apache/grails-core/issues/16122
### Summary
Grails 8 removed `grails.util.Metadata.get(Object)` and
`grails.util.Metadata.getProperty(String)`. Because both were the entry points
Groovy uses for dynamic/map-style access, any Grails 7 plugin or application
that reads a metadata key with `Metadata.current.'some.key'` or
`Metadata.current.get('some.key')` now fails at **runtime** with
`MissingPropertyException` / `MissingMethodException` instead of returning a
value or `null`.
This is not caught at compile time, so an unchanged Grails 7 binary plugin
installs, loads, and boots normally and then throws a 500 on the first request
that touches the code path.
Discovered while testing released Grails 7 plugins against `8.0.0-M5`.
### Grails Version
8.0.0-M5 (also expected on current 8.0.x)
### Java / Groovy Version
Java 21.0.11 (Corretto), Groovy 5.0.8, Spring Boot 4.1.0, Spring Framework
7.0.8
### Steps to Reproduce
Minimal reproduction in a stock `grails create-app --profile=web`
application generated with the published 8.0.0-M5 distribution:
```groovy
package probe
import grails.util.Metadata
import spock.lang.Specification
class MetadataAccessSpec extends Specification {
void 'dynamic key access on Metadata still works'() {
expect:
Metadata.current.'app.grails.version' == null
}
void 'map style get on Metadata still works'() {
expect:
Metadata.current.get('app.grails.version') == null
}
void 'supported typed accessor works'() {
expect:
Metadata.current.getProperty('app.grails.version', String, null) ==
null
}
}
```
Result on 8.0.0-M5:
```
MetadataAccessSpec > dynamic key access on Metadata still works FAILED
Caused by: groovy.lang.MissingPropertyException at
MetadataAccessSpec.groovy:10
MetadataAccessSpec > map style get on Metadata still works FAILED
Caused by: groovy.lang.MissingMethodException at
MetadataAccessSpec.groovy:15
3 tests completed, 2 failed
```
The third feature (the supported typed accessor) passes.
### Real plugin affected
`org.grails.plugins:grails-web-console:7.1.0`, an unchanged plugin released
for Grails 7, added to a stock 8.0.0-M5 web app as `runtimeOnly
"org.grails.plugins:grails-web-console:7.1.0"`.
The plugin resolves, is discovered, and the app starts. `GET /console` then
returns a 500:
```
ERROR o.g.web.errors.GrailsExceptionResolver : MissingPropertyException
occurred when processing request: [GET] /console
No such property: app.grails.version for class: grails.util.Metadata.
Stacktrace follows:
Caused by: groovy.lang.MissingPropertyException: No such property:
app.grails.version for class: grails.util.Metadata
at
org.grails.plugins.console.ConsoleController.index(ConsoleController.groovy:28)
~[grails-web-console-7.1.0.jar!/:na]
at
org.grails.core.DefaultGrailsControllerClass$ReflectionInvoker.invoke(DefaultGrailsControllerClass.java:215)
~[grails-core-8.0.0-M5.jar!/:8.0.0-M5]
at
org.grails.web.mapping.mvc.UrlMappingsInfoHandlerAdapter.handle(UrlMappingsInfoHandlerAdapter.groovy:130)
~[grails-web-url-mappings-8.0.0-M5.jar!/:8.0.0-M5]
```
### Expected Behaviour
An unchanged Grails 7 plugin reading a metadata key through the
dynamic/map-style API keeps working on Grails 8, or the breaking change is
documented in the Grails 8 upgrade guide with a stated migration path.
### Actual Behaviour
The access throws at runtime, and the removal is not mentioned anywhere in
`grails-doc/src/en/guide/upgrading/upgrading80x.adoc`.
### Origin of the change
The two methods were deprecated on the 7.0 line:
```groovy
@Deprecated(since = '7.0', forRemoval = true)
Object get(Object key) {
getProperty(key.toString(), Object, null)
}
@Deprecated(since = '7.0', forRemoval = true)
Object getProperty(String propertyName) {
get(propertyName)
}
```
and removed in commit `2110c45f367b9955da734afcc5dfd56d133941f7`
("refactor!: remove deprecated classes and methods") from
`grails-gradle/model/src/main/groovy/grails/util/Metadata.groovy`.
The deprecation itself was correct. The problem is the runtime-only failure
mode for already-published binary plugins, combined with the absence of any
upgrade-guide entry.
### Suggested resolution
Two options, not mutually exclusive:
1. Provide an opt-in compatibility shim in the same spirit as `grails {
legacyCommandSupport = true }` (#16011) and the proposed
`grails.legacy.holdersDuringDoWithSpring` (#16101), restoring dynamic/map-style
key access on `Metadata` with a one-time deprecation warning, so unchanged
Grails 7 plugins keep working during migration.
2. At minimum, document the removal in the Grails 8 upgrade guide, alongside
the existing API-break notes from #15823, with the replacement being
`getProperty(key, targetType, defaultValue)` / `navigate(...)` /
`getOrDefault(key, default)`.
### Notes
This was found in a broader compatibility sweep of released Grails 7 plugins
against 8.0.0-M5. Other plugins in the same sweep (`grails-export` 7.1.0,
`grails-mail` 5.0.3, `grails-shiro` 6.0.0, `cache-ehcache` 5.0.0-RC1) load and
function correctly, so plugin loading itself is working - this is specific to
the removed `Metadata` accessors.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]