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]

Reply via email to