////
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
////

This section covers all the new features introduced in Grails {grailsMajorVersion}

=== Overview

Grails {grailsMajorVersion} is a major release that includes new features, improvements, and dependency upgrades.
This release focuses on enhancing the developer experience, improving performance, and ensuring compatibility with the latest technologies.

For detailed information on how to upgrade to Grails {grailsMajorVersion}, including major dependency changes, please see the xref:upgrading#upgrading80x[Upgrading from Grails 7 to Grails {grailsMajorVersion}] section.
Notable new features are included below.

==== Platform Baseline

Grails {grailsMajorVersion} raises the standard build and runtime baseline to Java 21 and uses Gradle {gradleVersion}.
The standard Grails BOM remains on Groovy {groovyVersion} and Spock {spockVersion}, while Micronaut-enabled Grails applications use Micronaut-specific BOM variants that align with Micronaut 5 and require JDK 25 or later.

==== Spring Boot 4.1 and Spring Framework 7

Grails {grailsMajorVersion} is built on Spring Boot {springBootVersion} and Spring Framework {springVersion}.
This brings the Spring Boot 4 modular artifact layout, Spring Framework 7 API removals, Jackson 3, Tomcat 11 and Jakarta Servlet 6.1, plus Spring Boot 4.1 dependency management for Spring Security 7.1, Spring Data 2026.0 and Micrometer 1.17.

The Grails 8 upgrade guide calls out the major application-impacting changes and links to the Spring Boot 4.0 migration guide, Spring Boot 4.1 release notes and Spring Framework 7.0 release notes.

==== Hibernate 7 Application Generation

Grails Forge can now generate applications configured for Grails Data with Hibernate 7.
The `create-*` commands also accept a new `-d, --data` option (the previous `-g` and `--gorm` flags remain supported as legacy aliases) with the values `hibernate5`, `hibernate7`, and `mongodb`; the legacy value `hibernate` is still accepted and selects Hibernate 5:

[source,console]
----
grails -t forge create-app --data=hibernate7 com.example.demo
----

Applications generated for Hibernate 7 consume `grails-hibernate7-bom` — or `grails-hibernate7-micronaut-bom` when combined with the `grails-micronaut` feature — consistently across the application dependencies, the buildscript classpath, and `buildSrc`.
This avoids the dependency resolution conflicts that occur when an application generated for Hibernate 5 is manually switched to `grails-data-hibernate7` while the default `grails-bom` remains on the build classpath.
The `database-migration` feature also selects the matching `grails-data-hibernate7-dbmigration` plugin automatically.

==== Plugin Beans Register Before Spring Boot Auto-Configuration

Grails {grailsMajorVersion} unifies and retimes the plugin lifecycle so that the beans a plugin contributes are registered before Spring Boot processes its auto-configurations.
A plugin bean now takes precedence over a Spring Boot default guarded by `@ConditionalOnMissingBean` — Boot backs off in favour of the plugin's bean, with no need to override or remove it afterwards.

Plugins register beans through the new `beanRegistrar()` hook, which returns a Spring Framework {springapi}org/springframework/beans/factory/BeanRegistrar.html[BeanRegistrar]. Because `BeanRegistrar` is a functional interface, a closure coerced with `as BeanRegistrar` is all that is required:

[source,groovy]
----
import org.springframework.beans.factory.BeanRegistrar
import org.springframework.beans.factory.BeanRegistry
import org.springframework.core.env.Environment
import grails.plugins.Plugin

class MyGrailsPlugin extends Plugin {

    @Override
    BeanRegistrar beanRegistrar() {
        { BeanRegistry registry, Environment environment ->
            registry.registerBean('myService', MyServiceImpl)
        } as BeanRegistrar
    }
}
----

`beanRegistrar()` replaces the `doWithSpring` bean builder DSL, which is deprecated in Grails {grailsMajorVersion}.
The DSL continues to work for now, but you are strongly urged to migrate.
See the xref:upgrading#upgrading80x[upgrade guide] for details.

==== CLI Commands Move to Companion `-cli` Artifacts

Grails commands (`ApplicationCommand` implementations such as the `dbm-*` database-migration
commands, the scaffolding `generate-*` commands, `url-mappings-report`, and `schema-export`) no
longer ship inside the runtime plugin artifacts.
Each command-bearing module now publishes a companion artifact under its own Maven coordinate with
a `-cli` suffix — for example `org.apache.grails:grails-core-cli` and
`org.apache.grails:grails-data-hibernate7-dbmigration-cli`.

The command contract itself moved from `grails.dev.commands.*` to `org.apache.grails.core.cli.*`
in the new `grails-core-cli` artifact.
Applications consume cli artifacts through the new `grailsCli` Gradle configuration, which is
compile-visible for `grails-app/commands` sources and on the command-runner classpath, but never on
`runtimeClasspath` — commands and their CLI-only dependencies (such as `grails-shell-cli`) are
excluded from `bootRun`, `bootJar`, and `bootWar` entirely.
The Grails Gradle plugin provisions `grailsCli` automatically: it adds `grails-core-cli` and
`grails-console`, and discovers every companion cli artifact advertised by the application's
dependency graph — a command-bearing plugin advertises its companion through the
`Grails-Cli-Artifact` manifest attribute of its runtime jar, and its commands are registered in a
`META-INF/grails-cli.factories` file inside the companion jar. Declaring a plugin is therefore all
that is needed; its commands (including the per-command Gradle tasks such as `dbmUpdate`) follow
automatically, even when the plugin is only a transitive dependency:

[source,groovy]
.build.gradle
----
dependencies {
    // the dbm-* commands are discovered automatically: the plugin jar advertises
    // org.apache.grails:grails-data-hibernate7-dbmigration-cli, which is added to grailsCli
    implementation 'org.apache.grails:grails-data-hibernate7-dbmigration'
}
----

The auto-provisioning can be disabled with `grails { cliAutoProvision = false }`, in which case
`grailsCli` dependencies are declared explicitly.

Plugin authors get the whole publishing side from the new
`org.apache.grails.gradle.grails-plugin-cli` Gradle plugin: it creates the `cli` source set as a
feature variant, wires the command contract onto its compile classpath, advertises the companion
from the runtime jar, and publishes it (via the Grails publish plugin) under the plugin's
coordinate with the `-cli` suffix — the framework's own command-bearing modules are built with the
same plugin.

This removes CLI-only dependencies — and their side effects, such as the WAR-deployment failure
caused by a CLI Spring Boot initializer on the runtime classpath — from packaged applications.

The split does not hold up application upgrades on plugin re-releases: a plugin that ships no
commands is unaffected, and a command-bearing plugin that has not yet been rebuilt for Grails 8
keeps its runtime functionality — only its commands are unavailable until it publishes a companion
`-cli` artifact. See the xref:upgrading#upgrading80x[upgrade guide] for the migration steps and the
full upgrade-impact breakdown.

==== GSP Tag Library Improvements

Grails {grailsMajorVersion} continues the move toward method-based TagLib handlers while preserving compatibility with existing closure-based tags.
Method-defined tags now bind named attributes more predictably, exclude inherited framework and `Object` methods from tag dispatch, and preserve real namespace property getters.

The Grails Gradle extension now defaults `preserveParameterNames` to `true`, so application Groovy compilation preserves method parameter names for features such as typed method TagLib arguments.

Tag library unit tests also clean up and rebuild TagLib metadata automatically between features.
Tests that use `TagLibUnitTest` no longer need to manage `purgeTagLibMetaClass`, and specs that mock additional tag libraries continue to work across feature methods.

==== Namespace-Aware Link Generation

Links, form actions, pagination links, sortable column links, redirects, chains, and includes now resolve the target controller namespace automatically when `namespace` is omitted.
In the normal case, where only one controller has the target name, `controller` and `action` are enough to generate the correct namespaced or non-namespaced URL.
If multiple controllers share the same name, specify `namespace` to choose one explicitly.
Use `namespace=""` in GSP markup, or `namespace: null` in Groovy code, to target a non-namespaced controller.

==== @GrailsCompileStatic on Controllers That Use Tag Libraries

Controllers annotated with `@GrailsCompileStatic` can now invoke tag library methods without compile-time errors.

Both calling patterns are supported out of the box:

[source,groovy]
----
import grails.compiler.GrailsCompileStatic

@GrailsCompileStatic
class BookController {

    def index() {
        // Direct call in the default namespace
        response.writer << link(controller: 'book', action: 'list')

        // Namespaced call via a dispatcher property
        response.writer << my.customTag(attr: 'value')
    }
}
----

`@GrailsCompileStatic` recognises controllers and tag libraries by convention and marks tag dispatch points as permissible dynamic calls, while leaving the rest of the class fully type-checked.

==== Opt All Controllers, Services and Tag Libraries into GrailsCompileStatic

Static compilation can now be enabled for every controller, service and tag library in an application from the nested `compileStatic` block of the `grails` build configuration, without annotating each class:

[source,groovy]
.build.gradle
----
grails {
    compileStatic {
        controllers = true
        services = true
        tagLibs = true
    }
}
----

All three options are disabled by default and independent, or `compileStatic { all = true }` can be used as a shortcut to enable all of them. Any class that declares its own `@CompileDynamic` (or `@GrailsCompileStatic`/`@CompileStatic`/`@GrailsTypeChecked`/`@TypeChecked`) annotation keeps that setting, so the opt-in never overrides an explicit per-class choice. Tag libraries that still declare tags as closure fields (the deprecated form) are skipped automatically by the `tagLibs` opt-in and left dynamically compiled with a build warning, so define tags as methods to opt them in. See the xref:staticTypeCheckingAndCompilation/grailsCompileStatic[GrailsCompileStatic] section for details.

==== Test HTTP Client Form Requests

Integration tests that implement `HttpClientSupport` can now use `httpPostForm(...)` to send `application/x-www-form-urlencoded`
request bodies. The helper URL-encodes form fields using UTF-8, and repeated values in a `Collection` or array are encoded as
repeated keys in insertion order.

==== Latency Testing Support

The new `grails-testing-support-latency` module injects artificial latency into the application under test, so that
requests randomly take longer. Slower responses widen the window in which timing bugs occur, turning intermittently
flaky functional tests — assertions that race a navigation, missing waits, ordering assumptions — into deterministic
failures. The module is inert until `grails.testing.latency.enabled` is set to `true`, and the delay range, the
fraction of requests affected, and the url patterns are all configurable. See the
xref:testing/functionalTesting[Functional Testing] section for details.

==== GORM for MongoDB: TTL, Text, and Reconciled Indexes

GORM for MongoDB can now declare a https://www.mongodb.com/docs/manual/core/index-ttl/[TTL index] directly in the mapping DSL.
Adding `expireAfterSeconds` to the `indexAttributes` of a single date-valued property creates a TTL index, and MongoDB
automatically removes each document once it reaches the configured age:

[source,groovy]
----
class LoginEvent {
    Date dateCreated
    static mapping = {
        dateCreated index: true, indexAttributes: [expireAfterSeconds: 3600]
    }
}
----

A single property can likewise declare a `$text` index — or another special type such as `2dsphere` — through
`indexAttributes: [type: 'text']`.

When the options of an already-created index change between restarts, GORM now reconciles the difference instead of only
logging a conflict. A changed TTL is applied in place with MongoDB's `collMod` command — no drop, no rebuild — while any
other option change is applied by declaring `indexAttributes: [recreateOnConflict: true]`.

==== GORM for MongoDB and Spring Data MongoDB Interoperability

A new optional module, `grails-data-mongodb-spring-data`, lets an application use GORM for MongoDB and
https://spring.io/projects/spring-data-mongodb[Spring Data MongoDB] — its `MongoTemplate` and repositories — side by
side over the *same* `MongoClient`, database and codecs, and, within a single `@Transactional` method, the *same*
MongoDB transaction. When the module and `spring-data-mongodb` are on the classpath of a Spring Boot application that
already has a GORM `MongoDatastore`, it auto-configures a `MongoDatabaseFactory`, a `MongoTemplate` and a primary
`transactionManager` over GORM's existing connection — GORM keeps ownership of the client:

[source,groovy]
----
@Transactional
void transfer(MongoTemplate mongoTemplate) {
    new Account(name: "from").save()                  // GORM
    mongoTemplate.insert(new LedgerEntry(amount: 10)) // Spring Data
    // both commit together, or neither is applied if an exception is thrown
}
----

The unified transaction shares GORM's server-side `ClientSession`, so it requires GORM multi-document transactions to
be enabled (`grails.mongodb.transactional = true`). Spring Data repositories are enabled the usual way with
`@EnableMongoRepositories`, on a package separate from the GORM `@Entity` classes; only the connection, codecs and
session are shared, and the two object-mapping models stay separate. See the
link:{mongodb5Guide}index.html#springDataInterop[Spring Data MongoDB Interoperability] section of the GORM for MongoDB
guide for details.
