close
Skip to content

8.x Add Class-typed getAttribute reads for session, request and servletContext#15732

Merged
codeconsole merged 6 commits into
apache:8.0.xfrom
codeconsole:feat/typed-attribute-reads
Jun 16, 2026
Merged

8.x Add Class-typed getAttribute reads for session, request and servletContext#15732
codeconsole merged 6 commits into
apache:8.0.xfrom
codeconsole:feat/typed-attribute-reads

Conversation

@codeconsole

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #15715. The named converters (string, int, boolean, …) cover scalar attribute values, but attributes frequently hold richer objects — a security principal, a domain instance, a CSRF token — where today the only statically-compilable read is a cast at every call site:

CsrfToken token = (CsrfToken) session.getAttribute('_csrf')
UserDetails user = request.getAttribute(USER_ATTRIBUTE) as UserDetails

This adds a Class-typed overload of getAttribute (plus a default-value variant) to session, request and servletContext:

CsrfToken token   = session.getAttribute('_csrf', CsrfToken)
UserDetails user  = request.getAttribute('apiUser', UserDetails)
Theme theme       = session.getAttribute('theme', Theme, Theme.DEFAULT)

The generic signature (static <T> T getAttribute(holder, String name, Class<T> type)) lets the static compiler infer the return type from the class literal, so the read compiles under @CompileStatic / @GrailsCompileStatic with no cast.

Design

  • Typed read, not coercion. The attribute is returned only when it is an instance of the requested type; absent and wrong-typed attributes read as null (so the default-value overload also applies on a type mismatch). No TypeConverters delegation — the named converters remain the coercing API, the Class overload is the type-safe retrieval API. Two simple contracts instead of one mixed one.
  • Overloads getAttribute rather than introducing a new verb. Mirrors grails.config.Config.getProperty(String key, Class<T> targetType, T defaultValue) (and Spring's Environment.getProperty(key, type, default)), and shows up in IDE completion right next to the raw getAttribute(String).
  • Scope: the three attribute holders only. flash is skipped — it is a Map, and Groovy's DGM Map.get(key, default) (which inserts the default) makes a two-arg sibling on flash a semantic trap. params is skipped — its values are Strings, where a non-coercing typed read is not useful.

Tests & docs

  • HttpSessionExtensionSpec, HttpServletRequestExtensionSpec, ServletContextExtensionSpec each gain two features: instance-match / supertype-match / absent / wrong-type-reads-as-null semantics, default-value behavior, and a @CompileStatic static-resolution guard (the guard fails test compilation without the new extension methods).
  • New "Typed Reads of Attributes" subsection in the controllers guide, following the "Type Conversion of Attributes" section from Add typed attribute converters for session, request, flash and servletContext #15715.

:grails-web-core:test passes in full.

@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 48.4425%. Comparing base (1c2d8a6) to head (b1fc911).
⚠️ Report is 24 commits behind head on 8.0.x.

Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##                8.0.x     #15732        +/-   ##
==================================================
+ Coverage     48.3255%   48.4425%   +0.1169%     
- Complexity      15216      15284        +68     
==================================================
  Files            1875       1875                
  Lines           85818      85873        +55     
  Branches        14969      14978         +9     
==================================================
+ Hits            41472      41599       +127     
+ Misses          37975      37863       -112     
- Partials         6371       6411        +40     
Files with missing lines Coverage Δ
...ils/web/servlet/HttpServletRequestExtension.groovy 21.5054% <100.0000%> (+6.3891%) ⬆️
...org/grails/web/servlet/HttpSessionExtension.groovy 55.5556% <100.0000%> (+10.7280%) ⬆️
.../grails/web/servlet/ServletContextExtension.groovy 33.3333% <100.0000%> (+17.9487%) ⬆️

... and 12 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codeconsole codeconsole changed the title Add Class-typed getAttribute reads for session, request and servletContext 8.x Add Class-typed getAttribute reads for session, request and servletContext Jun 12, 2026
@codeconsole codeconsole force-pushed the feat/typed-attribute-reads branch from a5f6083 to b1fc911 Compare June 12, 2026 16:21
@bito-code-review

Copy link
Copy Markdown

Using value?.class?.isAssignableFrom is not necessary here because the code already resolves the type and uses isInstance to check compatibility. The current implementation resolvedType.isInstance(value) is the standard, idiomatic way to check if an object is an instance of a specific class in Java/Groovy, and it correctly handles null values (returning false).

grails-web-core/src/main/groovy/org/grails/web/servlet/HttpServletRequestExtension.groovy

Object value = request.getAttribute(name)
        Class<T> resolvedType = (Class<T>) ClassUtils.resolvePrimitiveIfNecessary(type)
        resolvedType.isInstance(value) ? resolvedType.cast(value) : null

…ubclass proxies (the CGLIB/Hibernate mechanism)
@testlens-app

This comment has been minimized.

Comment thread grails-web-core/build.gradle Outdated
…sm) rather than a JDK proxy + plain subclass
@codeconsole codeconsole force-pushed the feat/typed-attribute-reads branch from 7db9587 to 145bc47 Compare June 16, 2026 18:37
@codeconsole codeconsole merged commit eb79b3e into apache:8.0.x Jun 16, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants