Skip to content

Clear omitted bindData fields when nullMissing is enabled - #15950

Open
jamesfredley wants to merge 5 commits into
fix/binddata-mass-assignmentfrom
fix/binddata-null-missing-stale-data
Open

Clear omitted bindData fields when nullMissing is enabled#15950
jamesfredley wants to merge 5 commits into
fix/binddata-mass-assignmentfrom
fix/binddata-null-missing-stale-data

Conversation

@jamesfredley

@jamesfredley jamesfredley commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Description

Adds opt-in stale-data clearing to bindData, stacked on the binding base from #15947.

When nullMissing: true is supplied together with an explicit include list, included properties omitted from the source are set to null. Existing behavior is unchanged when the option is absent, false, or no explicit include was supplied.

Final contract

Area Behavior
Opt-in bindData(target, source, [include: [...], nullMissing: true])
Default Omitted fields retain their existing value
Authorization Explicit includes and (in secure mode) generated binding allowlists authorize clearing
Denials Excludes, bindable: false, and framework-managed properties remain protected
Paths Nested objects, lists, arrays, maps, indexed paths, and filters are supported
Wildcards Wildcard matching is isolated to nullMissing; it cannot broaden normal request binding
Compatibility Existing public overloads continue to delegate with nullMissing = false

Binding base (from #15947)

This branch merges #15947, which keeps data binding permissive by default and adds an opt-in deny-by-default secure mode (grails.databinding.legacyBindableDefault=false). The merge preserves #15947's exact/nested-prefix binding predicate and its bindable: false enforcement (always honored, including the nested Map-constructor fallback). Generated entries such as foo_* or foo.* cannot authorize a sibling property such as foo_admin.

Indexed includes and excludes are normalized only for nullMissing path matching. Explicit includes do not bypass recursive bindable: false, framework-property, or exclusion checks.

Example

bindData book, params, [include: ['title', 'description'], nullMissing: true]

Verification

  • :grails-web-databinding:test
  • :grails-test-suite-web:test
  • :grails-test-suite-persistence:test
  • Focused BindDataMethodTests coverage for public APIs, explicit and generated allowlists, null-include safety, bindable: false, framework properties, nested/indexed collections and maps, filters, excludes, malformed indexes, and wildcard sibling protection
  • Merge conflict with Add opt-in deny-by-default data binding and always honor bindable:false #15947 resolved; the merged suite passes serially, with the special-properties spec restoring global Holders config to avoid cross-spec leakage

Related

PR Relationship
#15947 Binding base (permissive default + opt-in secure mode) merged into this PR
#15808 Earlier combined approach, closed as superseded by #15947 and #15950

Contributor Checklist

  • Tests cover the affected public behavior.
  • User-facing behavior is documented in the bindData reference and Grails 8 upgrade guide.
  • Default behavior and existing overloads remain compatible.
  • The PR is labeled ai-generated-starting-point.
  • All contributed code is provided under the Apache License 2.0.

Assisted-by: Sisyphus:gpt-5.6-sol [gpt-coding]

When nullMissing is true and an include allowlist is provided, omitted
allowlisted properties are set to null. Default remains leave-unchanged.

Assisted-by: Sisyphus:xai/grok-4.5 [gpt-coding]

Copilot AI 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.

Pull request overview

Adds opt-in “null missing” semantics to bindData so that, when nullMissing: true is provided alongside an explicit include allowlist, included properties omitted from the binding source are actively cleared (null) rather than leaving stale values on the target object. This extends Grails’ web data binding behavior to better support typical “edit/update” form semantics without enabling the behavior by default.

Changes:

  • Introduces a nullMissing option plumbed through DataBinderDataBindingUtils, and applies clearing only when an explicit include list is provided.
  • Implements missing-field clearing logic in DataBindingUtils (including nested indexed collection paths and map-indexed paths).
  • Adds test coverage and updates documentation + upgrading notes to describe the new opt-in behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
grails-web-databinding/src/main/groovy/grails/web/databinding/DataBindingUtils.java Adds nullMissing overloads and implements missing included-property clearing logic after binding.
grails-web-databinding/src/main/groovy/grails/web/databinding/DataBinder.groovy Wires nullMissing: true from the bindData options map into the binding call.
grails-test-suite-web/src/test/groovy/org/grails/web/servlet/BindDataMethodTests.groovy Adds new controller-backed specs covering nullMissing clearing, excludes, nested indexed paths, map paths, and bindable whitelist interactions.
grails-doc/src/en/ref/Controllers/bindData.adoc Documents nullMissing usage and constraints (opt-in; requires include).
grails-doc/src/en/guide/upgrading/upgrading80x.adoc Notes the behavior change for Grails 8.x upgrades (opt-in; only with include).

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

@bito-code-review

Copy link
Copy Markdown

The observation regarding isPropertyAllowedByWhitelist is accurate. The current implementation relies on exact matches and simple prefix checks, which fails to account for the wildcard semantics used by the binder (e.g., address.* or address_*). Updating the whitelist evaluation to support these patterns would ensure that nested properties are correctly identified as bindable, allowing nullMissing to function consistently across nested paths.

@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 1.79949% with 382 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.8298%. Comparing base (f1bdc75) to head (62d4aad).

Files with missing lines Patch % Lines
...roovy/grails/web/databinding/DataBindingUtils.java 1.8470% 369 Missing and 3 partials ⚠️
.../groovy/grails/databinding/SimpleDataBinder.groovy 0.0000% 5 Missing and 5 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@                          Coverage Diff                           @@
##             fix/binddata-mass-assignment     #15950        +/-   ##
======================================================================
- Coverage                         51.4072%   50.8298%   -0.5774%     
+ Complexity                          17830      17675       -155     
======================================================================
  Files                                2039       2041         +2     
  Lines                               96004      96347       +343     
  Branches                            16692      16827       +135     
======================================================================
- Hits                                49353      48973       -380     
- Misses                              39296      40034       +738     
+ Partials                             7355       7340        -15     
Files with missing lines Coverage Δ
.../groovy/grails/databinding/SimpleDataBinder.groovy 72.9268% <0.0000%> (-1.6368%) ⬇️
...roovy/grails/web/databinding/DataBindingUtils.java 25.2226% <1.8470%> (-30.8585%) ⬇️

... and 109 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.

Preserve the strict binding allowlist while adding opt-in stale-data clearing, including indexed paths and explicit include handling.

Assisted-by: opencode:gpt-5.6-sol
@jamesfredley

Copy link
Copy Markdown
Contributor Author

Final update pushed in 018220fb3e.

The branch now incorporates #15947 as its deny-by-default security base and keeps nullMissing as a focused opt-in behavior. Review fixes cover wildcard sibling authorization, indexed excludes and includes, explicit include semantics, null generated allowlists, public boolean overload safety, recursive bindable: false, and framework-managed properties.

Verification passed for :grails-databinding-core:test, :grails-databinding:test, :grails-web-databinding:test, and :grails-test-suite-web:test. The final delta completed three rounds of Oracle and Codex review; all findings within #15950's six-file stacked scope are resolved.

@jamesfredley jamesfredley moved this to Todo in Apache Grails Jul 24, 2026
@borinquenkid

Copy link
Copy Markdown
Member

Thanks for splitting this out — since #15950 is branched directly off #15947, its diff currently duplicates all of #15947's changes on top of the nullMissing additions, which means reviewers end up reviewing the mass-assignment hardening twice.

Would you be open to one of:

  1. Retargeting this PR's base branch to fix/binddata-mass-assignment (the Add opt-in deny-by-default data binding and always honor bindable:false #15947 branch), so this shows only the incremental nullMissing commits — GitHub will auto-retarget it to 8.0.x once Add opt-in deny-by-default data binding and always honor bindable:false #15947 merges, or
  2. Folding this PR's commits into Add opt-in deny-by-default data binding and always honor bindable:false #15947 so it's reviewed as a single PR.

Whichever is easier on your end — just flagging so we don't duplicate review effort.

@borinquenkid borinquenkid added this to the grails:8.0.0-RC1 milestone Jul 28, 2026
@jamesfredley
jamesfredley changed the base branch from 8.0.x to fix/binddata-mass-assignment July 29, 2026 13:45
@jamesfredley

Copy link
Copy Markdown
Contributor Author

PR updated to target fix/binddata-mass-assignment

@testlens-app

testlens-app Bot commented Jul 29, 2026

Copy link
Copy Markdown

✅ All tests passed ✅

🏷️ Commit: 62d4aad
▶️ Tests: 15591 executed
⚪️ Checks: 54/54 completed


Learn more about TestLens at testlens.app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

4 participants