codeconsole opened a new pull request, #15721:
URL: https://github.com/apache/grails-core/pull/15721
## Summary
Change GORM's default constraint so an **unconstrained persistent property
is nullable** (optional) rather than required. Today Grails applies an implicit
`nullable: false` to every persistent property and every `Validateable`
command‑object field; this PR flips that default to `nullable: true`.
Two‑line change:
- `DefaultConstraintEvaluator` — domain‑class default → nullable
- `Validateable.defaultNullable()` — command‑object default → nullable
## Why this should be the Grails 8 default
**Grails is the only mainstream JVM data framework that is
required‑by‑default.** Every comparable layer treats an unconstrained property
as nullable and makes you opt *into* "required":
| Framework | Default for an unconstrained reference property |
|---|---|
| JPA / Hibernate | nullable (`@Column(nullable=false)` / `@NotNull` to
require) |
| Spring Data JPA | nullable |
| Spring Data MongoDB | nullable (schemaless) |
| Micronaut Data | nullable (`@NonNull` / Kotlin non‑null type to require) |
| Jakarta Bean Validation (JSR‑380) | nullable — *no constraint = valid*;
`@NotNull` is the opt‑in |
| **Grails / GORM (today)** | **required** (`nullable: true` to allow null) |
The case for flipping it:
1. **Principle of least surprise / ecosystem consistency.** Developers
arriving from Spring Boot, Micronaut, JPA, or plain Bean Validation expect
"nullable unless I say otherwise." Grails inverts that — one of the most common
first‑week surprises ("why does saving fail when I didn't mark anything
required?").
2. **It contradicts the storage layer.** SQL columns are nullable by
default; MongoDB is schemaless. Grails' validation default is *stricter than
the database it maps to*, for no structural reason.
3. **It inverts the JSR‑380 contract.** Bean Validation defines "absence of
a constraint ⇒ the value is valid (including null)"; `@NotNull` is the explicit
opt‑in. Grails' implicit `nullable:false` is a silent, framework‑specific
reversal of the spec it otherwise embraces.
4. **It breaks composition.** Fields contributed by traits, base classes, or
shared modules — or simply a new field added to an existing domain — silently
become **required**, surfacing only as a runtime `ValidationException` on the
first save (often in an unrelated path, e.g. a BootStrap seed). Mixing in
reusable field sets is exactly the modular design Grails 8 should encourage;
required‑by‑default punishes it.
5. **A major version is the right time.** Defaults that no longer match the
ecosystem should be corrected at a major boundary. Grails 8 is that boundary.
## Backward compatibility
Deliberate behavior change, with a **one‑line opt‑out** to restore the
legacy behavior:
```groovy
// grails-app/conf/application.groovy — restore required-by-default for
domains
grails.gorm.default.constraints = {
'*'(nullable: false)
}
```
```groovy
// command objects
static boolean defaultNullable() { false }
```
The existing `grails.gorm.default.constraints` machinery already applies
`'*'` constraints *before* the framework default and skips the default when one
is set (`canApplyNullableConstraint`), so the opt‑out composes cleanly with
per‑property overrides.
## Notes / scope
- Intentionally a small, focused diff to surface the proposal. The
framework's own test suite and docs assert required‑by‑default in many places
and would need a sweep if accepted — happy to follow up, or to gate the new
default behind an explicit `grails.gorm.default.nullable` flag (defaulting to
`true`) if preferred.
- Opening against `8.0.x` for discussion.
--
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]