(Quick Reference)

3 Upgrading from the previous versions

Version: 8.0.0-SNAPSHOT

3 Upgrading from the previous versions

3.1 Upgrading from Grails 7 to Grails 8

To ensure compatibility with Grails 8.0.0-SNAPSHOT, please review the following upgrade instructions carefully. This guide outlines the necessary steps and warnings to upgrade your Grails project from Grails 7.x to Grails 8.

Before you start: add the Spring Boot properties migrator. It is the fastest way to find deprecated or relocated configuration properties in your application.yml / application.groovy. Add it as a runtimeOnly dependency for the duration of your migration:

build.gradle
runtimeOnly 'org.springframework.boot:spring-boot-properties-migrator'

Boot the application once; the migrator logs warnings for every deprecated property it finds and, in many cases, applies the rename automatically at runtime so the app keeps starting. Remove the dependency once your configuration is clean.

1. Java 21 Minimum Requirement

Grails 8 requires a minimum of Java 21 to both build and run Grails applications. This is an increase from Grails 7, which required Java 17. Update your project’s toolchain, CI pipelines, and deployment environments to use JDK 21 or later.

2. Spring Boot 4.0.x

Grails 8 upgrades from Spring Boot 3.5.x to Spring Boot 4.0.x. This is a major upgrade that brings Spring Framework 7.0.x, new module structure, and several breaking changes.

For full details, consult:

3. Spring Boot Autoconfigure Modularization

Spring Boot 4 split the monolithic spring-boot-autoconfigure module into domain-specific modules. Auto-configuration classes have moved to new packages. If your application imports or references auto-configuration classes directly, you will need to update those references.

Notable module changes:

  • Web MVC auto-configuration classes moved to org.springframework.boot:spring-boot-webmvc

  • Servlet auto-configuration classes moved to org.springframework.boot:spring-boot-servlet

  • MongoDB auto-configuration classes moved to org.springframework.boot:spring-boot-mongodb

For example, if your code imports MongoAutoConfiguration:

// Before (Grails 7 / Spring Boot 3.x)
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration

// After (Grails 8 / Spring Boot 4.x)
import org.springframework.boot.mongodb.autoconfigure.MongoAutoConfiguration

In addition to the auto-configuration class moves, two foundational interfaces were relocated out of nested packages and into org.springframework.boot directly:

Before (Spring Boot 3.x) After (Spring Boot 4.x)

org.springframework.boot.env.EnvironmentPostProcessor

org.springframework.boot.EnvironmentPostProcessor

org.springframework.boot.BootstrapRegistry and related types in org.springframework.boot

org.springframework.boot.bootstrap.BootstrapRegistry, org.springframework.boot.bootstrap.BootstrapRegistryInitializer, org.springframework.boot.bootstrap.ConfigurableBootstrapContext

The old locations are deprecated but retained for upgrade ease. If your application or plugin contributes an EnvironmentPostProcessor or BootstrapRegistryInitializer, update both the import and the corresponding META-INF/spring.factories key:

META-INF/spring.factories - Before
org.springframework.boot.env.EnvironmentPostProcessor=com.example.MyPostProcessor
META-INF/spring.factories - After
org.springframework.boot.EnvironmentPostProcessor=com.example.MyPostProcessor

4. MongoDB Configuration Property Changes

Spring Boot 4 renamed the MongoDB configuration property namespace for Spring Boot’s own auto-configuration. If your application.yml or application.groovy uses spring.data.mongodb. properties for Spring Boot auto-configuration, you must rename them to spring.mongodb.. Note that Grails GORM for MongoDB uses its own mongodb.* configuration namespace, which is unaffected by this change.

application.yml - Before (Grails 7)
spring:
    data:
        mongodb:
            host: localhost
            port: 27017
            database: mydb
application.yml - After (Grails 8)
spring:
    mongodb:
        host: localhost
        port: 27017
        database: mydb

5. Hibernate ORM Package Relocations

Spring Framework 7 removed the org.springframework.orm.hibernate5 package entirely. Grails 8 vendors these classes from Spring Framework 6.2.x into a new module (grails-data-hibernate5-spring-orm) under the package org.grails.orm.hibernate.support.hibernate5.

This change is transparent for most applications since Grails manages the Hibernate integration internally. However, if your application directly imports any of the following classes, you must update your import statements:

Before (Spring Framework 6 / Grails 7) After (Grails 8)

org.springframework.orm.hibernate5.HibernateTemplate

org.grails.orm.hibernate.support.hibernate5.HibernateTemplate

org.springframework.orm.hibernate5.HibernateTransactionManager

org.grails.orm.hibernate.support.hibernate5.HibernateTransactionManager

org.springframework.orm.hibernate5.LocalSessionFactoryBean

org.grails.orm.hibernate.support.hibernate5.LocalSessionFactoryBean

org.springframework.orm.hibernate5.SessionFactoryUtils

org.grails.orm.hibernate.support.hibernate5.SessionFactoryUtils

org.springframework.orm.hibernate5.SessionHolder

org.grails.orm.hibernate.support.hibernate5.SessionHolder

org.springframework.orm.hibernate5.HibernateCallback

org.grails.orm.hibernate.support.hibernate5.HibernateCallback

org.springframework.orm.hibernate5.support.OpenSessionInViewInterceptor

org.grails.orm.hibernate.support.hibernate5.support.OpenSessionInViewInterceptor

All other classes from org.springframework.orm.hibernate5 follow the same pattern - replace the org.springframework.orm.hibernate5 prefix with org.grails.orm.hibernate.support.hibernate5.

6. HttpStatus.MOVED_TEMPORARILY Removed

Spring Framework 7 removed the deprecated HttpStatus.MOVED_TEMPORARILY constant. Use HttpStatus.FOUND instead - both represent HTTP 302 with no behavioral change.

If your application references HttpStatus.MOVED_TEMPORARILY directly (for example, in custom redirect logic or interceptors), update the reference:

// Before
import org.springframework.http.HttpStatus
response.status = HttpStatus.MOVED_TEMPORARILY.value()

// After
import org.springframework.http.HttpStatus
response.status = HttpStatus.FOUND.value()

7. Micronaut Integration Changes

7.1 CLASSIC Boot Loader Removed

Spring Boot 4 removed the CLASSIC loader implementation (LoaderImplementation.CLASSIC). In Grails 7, the Grails Gradle Plugin automatically configured bootJar and bootWar tasks to use the CLASSIC loader when the grails-micronaut plugin was detected. This was required for java -jar execution to work correctly with the Micronaut-Spring integration.

In Grails 8, this automatic configuration has been removed since the CLASSIC loader no longer exists in Spring Boot 4.

Action required:

  • If you have explicitly set loaderImplementation in your build.gradle, remove it:

build.gradle - remove this block
// This is no longer valid in Spring Boot 4 - remove it
tasks.withType(BootArchive).configureEach {
    loaderImplementation = org.springframework.boot.loader.tools.LoaderImplementation.CLASSIC
}
  • The grails-micronaut plugin continues to work with Spring Boot 4’s default loader. If you experience issues running your Micronaut-enabled Grails application via java -jar, please report them to the Grails issue tracker.

7.2 enforcedPlatform Required for grails-bom

When using Micronaut, the grails-bom must be applied as enforcedPlatform instead of platform. The Micronaut 5 platform declares its own managed dependency versions that conflict with those managed by the grails-bom. Without enforcedPlatform, Gradle’s default conflict resolution (highest version wins) allows the Micronaut platform to silently override grails-bom versions, leading to binary incompatibilities at runtime.

For example, without enforcedPlatform:

  • Groovy is upgraded from 4.0.x to 5.0.x — Groovy 5 has breaking API changes (e.g. ArrayGroovyMethods moved) that cause NoClassDefFoundError at runtime

  • Spock is upgraded from 2.3-groovy-4.0 to 2.4-groovy-5.0 — compiled against Groovy 5, incompatible with Groovy 4 classes

  • Kotlin, Mockito, Byte Buddy are upgraded to versions not tested with the Grails stack

Using enforcedPlatform makes all grails-bom constraints strictly versioned so they cannot be overridden by any transitive dependency.

The Grails Gradle Plugin will error at configuration time if grails-micronaut is detected and grails-bom is not applied as enforcedPlatform.

build.gradle
dependencies {
    // enforcedPlatform is required when using grails-micronaut
    implementation enforcedPlatform("org.apache.grails:grails-bom:$grailsVersion")

    implementation 'org.apache.grails:grails-micronaut'
}
Applications generated by the Grails Forge automatically use enforcedPlatform when the Micronaut feature is selected.
7.3 Micronaut Features Require JDK 25

While the Grails 8 baseline remains JDK 21, any Grails application that uses the Micronaut integration must run on JDK 25 or later.

Micronaut Core’s internal io.micronaut.core.propagation.ScopedValues class references java.lang.ScopedValue.CallableOp, which only exists in JDK 25. ScopedValue was a preview API in JDK 21–24 and was finalized by JEP 506 in JDK 25, at which point its inner type was renamed from Callable to CallableOp. Running Micronaut on JDK 21–24 therefore fails at runtime with:

java.lang.NoClassDefFoundError: java/lang/ScopedValue$CallableOp
    at io.micronaut.core.propagation.ScopedValues.propagate(ScopedValues.java:45)
    ...

This affects any code path that goes through Micronaut’s HTTP client, filters, or context propagation — including the declarative @Client interfaces and micronaut-http-client-jdk.

Action required:

  • If your Grails 8 application uses grails-micronaut, micronaut-http-client, or any other Micronaut feature, upgrade your build, CI, and deployment JDK to JDK 25 or later.

  • Grails applications that do not use any Micronaut integration are unaffected and continue to run on JDK 21.

The Grails Forge generator enforces this requirement: selecting any Micronaut feature with a JDK version below 25 will fail with IllegalArgumentException at generation time.

8. Enum Serialization Default Changed

As announced in the Grails 7.0.2 deprecation notice, the SimpleEnumMarshaller is now the default for JSON and XML enum serialization in Grails 8. Enums are serialized as simple string values (e.g., "SUBMIT") instead of the verbose legacy format with type metadata.

If you previously opted in via configuration, you can now remove it:

application.yml - this is now the default and can be removed
grails:
    converters:
        json:
            enum:
                format: simple
        xml:
            enum:
                format: simple

If you need to restore the legacy behavior temporarily, the deprecated marshallers remain available but will be removed in a future release.

9. Jackson 3 is the New Default

Spring Boot 4 ships Jackson 3 (tools.jackson.*) and auto-configures a JsonMapper bean instead of the Jackson 2 ObjectMapper. Grails 8 follows that change: any Grails module that previously declared com.fasterxml.jackson.core:jackson-databind (Jackson 2) now declares tools.jackson.core:jackson-databind (Jackson 3, currently 3.1.0 via the Spring Boot 4 BOM).

What stayed the same:

  • Jackson annotations. All @JsonProperty, @JsonCreator, @JsonIgnore, @JsonInclude, @JsonIgnoreProperties, etc. remain at com.fasterxml.jackson.annotation.*. The Spring Boot 4 BOM explicitly permits the jackson-annotations 2.x artifact alongside the Jackson 3 BOM, so existing annotated DTOs and domain classes do not need to change.

  • The readTree(String), writeValueAsString(…​), and readValue(…​) method names on the mapper are unchanged.

What changed (and what you need to do if your code touches Jackson directly):

Concern Jackson 2 Jackson 3

Package

com.fasterxml.jackson.databind.*

tools.jackson.databind.*

Default mapper type

ObjectMapper

tools.jackson.databind.json.JsonMapper

Construction

new ObjectMapper()

JsonMapper.builder().build() (immutable; configuration locked after build())

Exceptions

JsonProcessingException / IOException (checked)

JacksonException extends RuntimeException (unchecked)

Default date format

Numeric timestamps

ISO-8601 strings

If you inject the auto-configured mapper: Spring Boot 4 provides a tools.jackson.databind.json.JsonMapper bean instead of com.fasterxml.jackson.databind.ObjectMapper. Update injection points to expect JsonMapper.

If you must stay on Jackson 2 temporarily: Spring Boot 4 still manages the Jackson 2 BOM (currently 2.21.2) and provides an opt-in toggle so the auto-configured mapper preserves Jackson 2 defaults:

application.yml
spring:
    jackson:
        use-jackson2-defaults: true

This is intended as a migration aid only; new code should target Jackson 3.

Spring Boot Jackson helper class renames. If your code uses Spring Boot’s Jackson helpers, rename them to the Jackson 3 equivalents:

Spring Boot 3.x (Jackson 2) Spring Boot 4.x (Jackson 3)

Jackson2ObjectMapperBuilderCustomizer

JsonMapperBuilderCustomizer

JsonObjectSerializer

ObjectValueSerializer

JsonValueDeserializer

ObjectValueDeserializer

@JsonComponent

@JacksonComponent

@JsonMixin

@JacksonMixin

Configuration property renames. The spring.jackson.* namespace was reorganized. The spring-boot-properties-migrator (see the tip at the top of this guide) will detect most of these and warn at boot time:

Before After

spring.jackson.read.*

spring.jackson.json.read.*

spring.jackson.write.*

spring.jackson.json.write.*

spring.jackson.parser.*

spring.jackson.json.read.* (where equivalents exist)

Module auto-detection. Jackson 3 auto-detects all Jackson modules on the classpath by default. If you need to disable this — for example, to avoid an unwanted module being picked up — set:

application.yml
spring:
    jackson:
        find-and-modules: false

10. Theme Support Removed

Spring Framework 7 removed the theme infrastructure entirely. The following theme-specific classes and interfaces no longer exist:

  • org.springframework.ui.context.ThemeSource

  • org.springframework.ui.context.Theme

  • org.springframework.ui.context.support.SimpleTheme

  • org.springframework.web.servlet.theme.SessionThemeResolver

JstlUtils itself still exists in Spring WebMVC 7.0, but its theme-related methods have been removed.

If your application uses Spring theme resolution (for example, switching CSS stylesheets via ThemeResolver), you must replace this with your own implementation. This was deprecated in Spring Framework 6.0 and fully removed in 7.0.

GSP layouts and views that relied on theme resolution will need to use alternative approaches such as configuration properties or session-based preferences managed in application code.

11. Embedded Server Module Relocations

Spring Boot 4 reorganized embedded server classes into separate modules. If your application or tests reference these classes directly, update the imports:

Before (Spring Boot 3.x / Grails 7) After (Spring Boot 4.x / Grails 8)

org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory

org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory

org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory

org.springframework.boot.web.server.servlet.ConfigurableServletWebServerFactory

org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext

org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContext

org.springframework.boot.web.context.WebServerApplicationContext

org.springframework.boot.web.server.context.WebServerApplicationContext

org.springframework.boot.web.servlet.context.AnnotationConfigServletWebApplicationContext

org.springframework.boot.web.context.servlet.AnnotationConfigServletWebApplicationContext

Applications using embedded Tomcat or Jetty should check for any direct class references and update accordingly. The spring-boot-tomcat module must now be an explicit dependency if you reference Tomcat classes directly.

Undertow is temporarily unsupported. Undertow has not yet been updated for Servlet 6.1, which Spring Boot 4 requires. As a result, Undertow is not an available embedded servlet container in Grails 8. Applications that previously used spring-boot-starter-undertow must switch to Tomcat or Jetty until Undertow ships Servlet 6.1 compatibility. The Grails Forge generator no longer offers Undertow as a selectable servlet implementation, and explicitly selecting it (for example, --servlet=undertow on the API/CLI) fails fast with IllegalArgumentException and a clear error message.

12. HandlerAdapter.getLastModified Removed

Spring Framework 7 removed the deprecated getLastModified method from the HandlerAdapter interface. If your application implements HandlerAdapter directly and overrides this method, remove the override. The framework no longer calls this method for Last-Modified header management.

13. Spring Security Filter Ordering

Spring Boot 4 removed the SecurityProperties.DEFAULT_FILTER_ORDER constant. Spring Security is moving toward the fluent HttpSecurity configuration API for filter chain management, rather than explicit filter ordering via constants.

If your application references DEFAULT_FILTER_ORDER for custom filter positioning, replace it with the concrete value -100 (computed as OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100). Longer term, consider migrating to the fluent HttpSecurity API for filter chain configuration.

14. Spring Boot Starter Renames

Spring Boot 4 renamed several starters as part of its modularization effort. If your build.gradle references any of the following, update the artifact id:

Before (Spring Boot 3.x) After (Spring Boot 4.x)

spring-boot-starter-web

spring-boot-starter-webmvc

spring-boot-starter-aop

spring-boot-starter-aspectj

spring-boot-starter-web-services

spring-boot-starter-webservices

spring-boot-starter-oauth2-authorization-server

spring-boot-starter-security-oauth2-authorization-server

spring-boot-starter-oauth2-client

spring-boot-starter-security-oauth2-client

spring-boot-starter-oauth2-resource-server

spring-boot-starter-security-oauth2-resource-server

build.gradle - Before
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-aop'
build.gradle - After
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
implementation 'org.springframework.boot:spring-boot-starter-aspectj'
If you want a faster, lower-friction migration that pulls in all Spring Boot modules without picking starters individually, Spring Boot 4 ships spring-boot-starter-classic (and spring-boot-starter-test-classic) as a transitional fallback. This is intended as a stopgap; new code should target the modular starters.

15. WAR Deployment Uses spring-boot-starter-tomcat-runtime

For WAR deployment to an external Servlet container, Spring Boot 4 introduced a dedicated spring-boot-starter-tomcat-runtime artifact that contributes only the integration classes needed to start under an external container, without bundling the embedded Tomcat distribution into the WAR.

Replace the previous "change scope to provided/`testImplementation`" idiom with the new starter:

build.gradle - Before
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
build.gradle - After
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat-runtime'

The Grails deployment guides have been updated to reflect this idiom. Embedded Tomcat usage (the default for grails run-app and bootable WARs) continues to use spring-boot-starter-tomcat unchanged.

16. Test Infrastructure Changes

Spring Boot 4 made several breaking changes to its test infrastructure. Most Grails tests use GrailsUnitTest, GrailsWebUnitTest, and Integration and are unaffected, but if your application uses @SpringBootTest directly the following apply.

@SpringBootTest no longer auto-configures MockMvc, WebClient, or TestRestTemplate. Add the corresponding @AutoConfigureMockMvc / @AutoConfigureWebClient / @AutoConfigureTestRestTemplate annotations to opt in.

// Before
@SpringBootTest
class MyControllerSpec extends Specification {
    @Autowired MockMvc mockMvc      // injected automatically
    @Autowired TestRestTemplate rest // injected automatically
}

// After
@SpringBootTest
@AutoConfigureMockMvc
@AutoConfigureTestRestTemplate
class MyControllerSpec extends Specification {
    @Autowired MockMvc mockMvc
    @Autowired TestRestTemplate rest
}

@MockBean and @SpyBean were removed. Replace them with @MockitoBean and @MockitoSpyBean from org.springframework.test.context.bean.override.mockito. The new annotations work as fields in test classes only — they cannot be declared in @Configuration classes.

// Before
import org.springframework.boot.test.mock.mockito.MockBean
@SpringBootTest
class MyServiceSpec extends Specification {
    @MockBean BookRepository repo
}

// After
import org.springframework.test.context.bean.override.mockito.MockitoBean
@SpringBootTest
class MyServiceSpec extends Specification {
    @MockitoBean BookRepository repo
}

MockitoTestExecutionListener was removed. If your test infrastructure registers it explicitly, remove the registration; Spring’s bean override support handles @MockitoBean directly.

TestRestTemplate package change. TestRestTemplate moved to org.springframework.boot.resttestclient. Update the import. A new RestTestClient and corresponding @AutoConfigureRestTestClient annotation are also available as a fluent alternative.

17. DevTools Live Reload Disabled by Default

Spring Boot 4 changed the default of spring.devtools.livereload.enabled from true to false. If you rely on browser live reload during development, opt in explicitly under the development environment so non-development environments still honor the new Spring Boot 4 default:

application-development.yml
spring:
    devtools:
        livereload:
            enabled: true
Apps generated by the Grails Forge with the spring-boot-devtools reloading option do set this property automatically in application-development.yml, restoring Grails 7 livereload behavior for the dev profile only. You can opt out by changing the value to false or by deleting the application-development.yml block after generation. Existing Grails 7 applications that opt in to devtools must add this configuration manually.

18. Spring Retry No Longer Managed

Spring Boot 4 removed spring-retry from its managed dependencies. Spring for Apache Kafka and Spring AMQP have moved off Spring Retry to Spring Framework’s new retry support; if you used Spring Retry transitively through those projects you may not need it any more.

If your application directly uses @Retryable, @EnableRetry, or @Recover, declare the dependency explicitly in build.gradle:

build.gradle
implementation 'org.springframework.retry:spring-retry'
The grails-bom pins a known-good Spring Retry version (2.0.x), so you do not need to specify a version when using enforcedPlatform("org.apache.grails:grails-bom:$grailsVersion"). Override the BOM-managed version in your build if you need a newer release.

19. Other Default Behavior Changes

Spring Boot 4 made several smaller default-behavior changes that you may notice but rarely require code changes:

  • Logback default charset is now UTF-8 for log files (aligning with the existing Log4j2 default). Console output uses Console#charset() if available, otherwise UTF-8. If your log scraping tooling assumed a platform-default charset, switch it to UTF-8.

  • Liveness and readiness probes are enabled by default on the Health endpoint. The liveness and readiness health groups are exposed automatically. To disable, set:

    application.yml
    management:
        endpoint:
            health:
                probes:
                    enabled: false
  • HttpMessageConverters is deprecated. If your application contributed HttpMessageConverter beans through this class, switch to the new ClientHttpMessageConvertersCustomizer and ServerHttpMessageConvertersCustomizer interfaces.

  • JSpecify nullability annotations. Spring Framework 7 deprecated org.springframework.lang.Nullable and org.springframework.lang.NonNull in favor of the JSpecify annotations (org.jspecify.annotations.Nullable, org.jspecify.annotations.NonNull). The Spring annotations still work, so this is non-blocking, but new code should prefer the JSpecify equivalents.

20. Known Plugin Incompatibilities

Some third-party plugins have not yet been updated for Spring Boot 4 / Spring Framework 7 compatibility. The following are known blockers at this time:

  • grails-spring-security - Uses ReflectionUtils.getApplication() which was removed in Spring Boot 4. Integration tests for modules depending on Spring Security are disabled until the plugin is updated.

  • SiteMesh 3 - The decorator/layout mechanism is not compatible with Spring Framework 7. Applications using grails-sitemesh3 should remain on the grails-layout plugin (SiteMesh 2.6.x) until SiteMesh 3 is updated.

Check the Grails issue tracker for the latest status of plugin compatibility.

21. Custom JSON View Converters

JSON views now use Groovy’s groovy.json.JsonGenerator implementation instead of the previous Grails-specific JSON generator infrastructure. If you previously customized JSON view rendering with Grails-specific generator classes, migrate that logic to a groovy.json.JsonGenerator.Converter implementation.

This change applies to JSON views rendered by the grails-views-gson module. The grails.converters.JSON.registerObjectMarshaller(…​) API is still used by the grails-converters module for render …​ as JSON and related converter-based rendering.

To migrate your custom converters:

  • Implement groovy.json.JsonGenerator.Converter (was previously grails.plugin.json.builder.JsonGenerator.Converter)

  • Change the file name of the converter registration service loader to src/main/resources/META-INF/services/groovy.json.JsonGenerator$Converter (was previously src/main/resources/META-INF/services/grails.plugin.json.builder.JsonGenerator$Converter)

22 Rendering Enum values as JSON

It is no longer possible to render a single enum value as JSON using the render(Number.ONE as JSON) syntax. Previously, rendering an enum value would produce a JSON string with the type and name of the enum value like:

{
  "enumType": "com.example.Number",
  "name": "ONE"
}

Rendering an enum value as JSON will now instead throw a ConverterException. See PR 15212 for more details on this change.

3.2 Upgrading from Grails 7.0 to Grails 7.1

To ensure compatibility with Grails 7.1.x, please review the following upgrade instructions carefully. This guide outlines the necessary steps & warnings to upgrade your Grails project.

1. Groovy Changes

The Grails Gradle Plugins now automatically disables invokedynamic for all GroovyCompile tasks (see #15293). If you have a manual tasks.withType(GroovyCompile) block in your build.gradle that sets indy = false, you can safely remove it:

build.gradle - remove this block (no longer needed)
  // This is now handled by the Grails Gradle Plugin - remove it
  tasks.withType(GroovyCompile).configureEach {
        groovyOptions.optimizationOptions.indy = false
  }

To re-enable invokedynamic, use the grails extension in your build.gradle:

build.gradle
  grails {
      indy = true
  }

2 Breaking changes

2.1 Plugin configuration and Spring Conditional Annotations

Spring Boot’s @Configuration related classes have support for conditional bean wiring. In prior versions of Grails, annotations such as @ConditionalOnProperty could not be used for configuration stored in a plugin’s plugin.groovy or plugin.yml files. To support plugin configuration, plugin loading & configuration were split in Grails 7.1.x. Plugin loading related code was thus reworked and may break some applications if they relied on their own plugin loader.

As a result of these changes, if your application extends the GrailsApplicationPostProcessor or DefaultGrailsPluginManager there are breaking constructor changes. A new class, PluginDiscovery, was extracted from the Grails Plugin Manager logic and is now a dependency of these classes.
2.2 Greedy Extension Parameter Matching in URL Mappings

Grails 7.1 introduces a new greedy extension parameter marker (+) for URL mappings that provides more intuitive handling of file extensions in URLs with multiple dots.

The Problem with Default Behavior

By default, Grails URL mappings with optional extensions split at the first dot in the path:

"/$id(.$format)?"(controller: 'user', action: 'profile')

When matching the URL /test.test.json: - id = test (stops at first dot) - format = test.json (everything after first dot)

This can be problematic when IDs legitimately contain dots, such as file names, qualified class names, or version numbers.

New Greedy Matching Behavior

The new + marker enables greedy matching, which splits at the last dot instead:

"/$id+(.$format)?"(controller: 'user', action: 'profile')

Now the same URL /test.test.json matches as: - id = test.test (everything up to last dot) - format = json (extension after last dot)

Syntax

The + marker is added after the variable name and before the optional marker:

Required parameter with greedy extension:

"/$id+(.$format)?"(controller: 'file', action: 'download')

Optional parameter with greedy extension:

"/$id+?(.$format)?"(controller: 'resource', action: 'show')
Use Cases

Greedy extension matching is particularly useful for:

  1. File downloads with complex names:

    "/files/$filename+(.$format)?"(controller: 'file', action: 'download')

    Matches /files/document.final.v2.pdffilename=document.final.v2, format=pdf

  2. Versioned resources:

    "/api/$resource+(.$format)?"(controller: 'api', action: 'show')

    Matches /api/user.service.v1.jsonresource=user.service.v1, format=json

  3. Qualified class names:

    "/docs/$className+(.$format)?"(controller: 'documentation', action: 'show')

    Matches /docs/com.example.MyClass.htmlclassName=com.example.MyClass, format=html

Behavior Details
  • With dots: The greedy marker splits at the last dot, treating everything before as the parameter value and everything after as the extension

  • Without dots: URLs without any dots match entirely as the parameter with no format

  • Extension optional: When the extension is marked optional (.$format)?, URLs work both with and without extensions

Examples:

"/$id+(.$format)?"(controller: 'resource', action: 'show')

// URL Matches:
/test.test.json    → id='test.test', format='json'
/test.json          id='test', format='json'
/simpletest        → id='simpletest', format=null
/foo.bar.baz.xml    id='foo.bar.baz', format='xml'
Backward Compatibility

The + marker is opt-in and fully backward compatible:

  • Existing URL mappings without the + marker continue to work as before (splitting at the first dot)

  • No changes are required to existing applications

  • The feature can be adopted incrementally on a per-mapping basis

2.3 GormService API Changes

The grails.plugin.scaffolding.GormService class has been updated to fix a thread-safety issue and improve API clarity.

Changes
  1. The resource field type changed from GormAllOperations<T> to Class<T>

  2. A new gormStaticApi field of type GormAllOperations<T> was added with thread-safe lazy initialization using @Lazy

  3. The constructor no longer instantiates the resource class - it now stores the Class reference directly

Migration Impact

If your code extends GormService or accesses its fields:

Accessing GORM operations: Previously the resource field provided GORM operations. Now use the gormStaticApi field instead:

// Before (7.1.x and earlier)
class MyService extends GormService<MyDomain> {
    void myMethod() {
        def result = resource.list()  // resource was GormAllOperations<T>
        // ...
    }
}

// After (7.1.x with fix)
class MyService extends GormService<MyDomain> {
    void myMethod() {
        def result = gormStaticApi.list()  // use gormStaticApi instead
        // ...
    }
}

Accessing the domain class: If you need the Class reference, the resource field is now properly typed as Class<T>:

// Before
Class<MyDomain> clazz = resource.getClass()  // awkward, resource was an instance

// After
Class<MyDomain> clazz = resource  // resource is now the Class itself
2.4 Enhanced Audit Metadata Support

Grails 7.1 introduces comprehensive audit metadata support with new annotations for tracking both temporal information (when changes occurred) and auditor information (who made changes). This aligns with Spring Data’s auditing model while maintaining GORM’s flexibility.

New Annotations

Four new annotations have been added to simplify audit tracking:

  • @CreatedDate - Automatically populated with the creation timestamp on insert

  • @LastModifiedDate - Automatically populated with the modification timestamp on insert and update

  • @CreatedBy - Automatically populated with the current auditor on insert

  • @LastModifiedBy - Automatically populated with the current auditor on insert and update

These annotations work with various temporal types including Date, LocalDateTime, Instant, OffsetDateTime, and ZonedDateTime.

Example Domain Class
import grails.gorm.annotation.CreatedBy
import grails.gorm.annotation.CreatedDate
import grails.gorm.annotation.LastModifiedBy
import grails.gorm.annotation.LastModifiedDate
import java.time.LocalDateTime

class Book {
    String title
    String author

    @CreatedDate
    LocalDateTime created

    @LastModifiedDate
    LocalDateTime modified

    @CreatedBy
    String createdBy

    @LastModifiedBy
    String modifiedBy

    static constraints = {
        title blank: false
        author blank: false
        // Auditor fields should be nullable for anonymous operations
        createdBy nullable: true
        modifiedBy nullable: true
    }
}
Configuring AuditorAware

To populate @CreatedBy and @LastModifiedBy fields, you need to provide an AuditorAware bean that returns the current auditor:

src/main/groovy/com/example/SpringSecurityAuditorAware.groovy
package com.example

import groovy.transform.CompileStatic
import org.grails.datastore.gorm.timestamp.AuditorAware
import org.springframework.security.core.Authentication
import org.springframework.security.core.context.SecurityContextHolder

@CompileStatic
class SpringSecurityAuditorAware implements AuditorAware<String> {

    @Override
    Optional<String> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext()
                                                              .getAuthentication()

        if (authentication == null || !authentication.isAuthenticated()) {
            return Optional.empty()
        }

        String username = authentication.getName()

        // Don't set auditor for anonymous users
        if (username == 'anonymousUser') {
            return Optional.empty()
        }

        return Optional.of(username)
    }
}

Register the bean in your Application.groovy:

grails-app/init/com/example/Application.groovy
import org.grails.datastore.gorm.timestamp.AuditorAware
import org.springframework.context.annotation.Bean

class Application extends GrailsAutoConfiguration {
    static void main(String[] args) {
        GrailsApp.run(Application, args)
    }

    @Bean
    AuditorAware<String> auditorAware() {
        return new SpringSecurityAuditorAware()
    }
}
Auditor Field Types

Auditor fields can be different types depending on your needs:

  • String - for username strings

  • Long - for user ID references

  • Custom types - for embedding user objects

Example with different auditor types:

class Document {
    String title

    @CreatedDate
    LocalDateTime created

    @CreatedBy
    String createdByUsername  // Store username as String

    @LastModifiedBy
    Long lastModifiedById     // Store user ID as Long

    static constraints = {
        createdByUsername nullable: true
        lastModifiedById nullable: true
    }
}
Constraints on Auditor Fields

Unlike timestamp fields, auditor fields can have constraints applied to them. This is useful for:

  • Setting maxSize on String auditors to match database column definitions

  • Applying validation rules

  • Customizing database mapping

class Task {
    String name

    @CreatedBy
    String createdBy

    @LastModifiedBy
    String modifiedBy

    static constraints = {
        name blank: false
        // Apply constraints to auditor fields
        createdBy nullable: true, maxSize: 100
        modifiedBy nullable: true, maxSize: 100
    }
}
Deprecation of @AutoTimestamp

The @AutoTimestamp annotation is now deprecated in favor of the more explicit @CreatedDate and @LastModifiedDate annotations:

// Old (deprecated)
@AutoTimestamp(AutoTimestamp.EventType.CREATED)
LocalDateTime created

@AutoTimestamp
LocalDateTime modified

// New (recommended)
@CreatedDate
LocalDateTime created

@LastModifiedDate
LocalDateTime modified

The @AutoTimestamp annotation is marked with @Deprecated(forRemoval = true) and will be removed in Grails 8.0.

Migration Guide

To migrate from @AutoTimestamp to the new annotations:

  1. Replace @AutoTimestamp(AutoTimestamp.EventType.CREATED) with @CreatedDate

  2. Replace @AutoTimestamp (or @AutoTimestamp(EventType.UPDATED)) with @LastModifiedDate

  3. Optionally add @CreatedBy and @LastModifiedBy fields for auditor tracking

  4. Implement and register an AuditorAware bean if using auditor fields

  5. Remove nullable: true constraints from timestamp fields (no longer needed)

  6. Keep nullable: true on auditor fields (required for anonymous operations)

Automatic Imports for Annotations

To reduce boilerplate when using audit annotations, you can enable automatic imports for common Grails annotations in your build.gradle:

build.gradle
grails {
    importGrailsCommonAnnotations = true
}

When enabled, this automatically imports:

  • jakarta.validation.constraints.* - Jakarta validation annotations

  • grails.gorm.annotation.* - All GORM annotations including @CreatedDate, @LastModifiedDate, @CreatedBy, @LastModifiedBy

  • grails.plugin.scaffolding.annotation.* - Scaffolding annotations (if grails-scaffolding is in classpath)

With this setting, you can use the annotations without explicit imports:

// No import statements needed!
class Book {
    String title

    @CreatedDate      // Automatically imported
    LocalDateTime created

    @LastModifiedDate // Automatically imported
    LocalDateTime modified

    @CreatedBy        // Automatically imported
    String createdBy

    @NotBlank         // jakarta.validation.constraints.NotBlank imported
    @Size(max = 255)  // jakarta.validation.constraints.Size imported
    String author
}
Custom Star Imports

You can also configure custom star imports for your own packages independently:

build.gradle
grails {
    starImports = ['java.util.concurrent', 'groovy.transform', 'com.myapp.annotations']
}

This allows you to use classes from these packages without explicit imports throughout your Groovy code. The starImports configuration works independently and will be combined with any imports from importGrailsCommonAnnotations or importJavaTime flags if those are also enabled.

2.5 Scaffolding Namespace View Defaults

Grails 7.1 introduces an opt-in feature for scaffolding that allows namespace-specific scaffolded templates to take priority over non-namespaced view fallbacks.

Background

Previously, when a namespace controller requested a view, the scaffolding plugin would only generate a scaffolded view if no view existed at all. This meant that if you had:

  • A namespace controller (e.g., namespace = 'admin')

  • A non-namespaced view in grails-app/views/event/index.gsp

  • A namespace-specific scaffolded template in src/main/templates/scaffolding/admin/index.gsp

The non-namespaced view would always be used, and the namespace-specific scaffolded template would be ignored.

New Behavior

With the new enableNamespaceViewDefaults configuration, namespace-specific scaffolded templates can now override non-namespaced view fallbacks. This provides better support for namespace-specific customization of scaffolded views.

Configuration

To enable this feature, add the following to your application.yml:

application.yml
grails:
    scaffolding:
        enableNamespaceViewDefaults: true
View Resolution Priority

When enableNamespaceViewDefaults is enabled, the view resolution priority for namespace controllers is:

  1. Namespace-specific view (e.g., grails-app/views/admin/event/index.gsp)

    • If exists → used (highest priority)

  2. Namespace-specific scaffolded template (e.g., src/main/templates/scaffolding/admin/index.gsp)

    • If exists and no namespace view → used (overrides fallback)

  3. Non-namespaced view fallback (e.g., grails-app/views/event/index.gsp)

    • Used if no namespace view or scaffolded template exists

  4. Non-namespaced scaffolded template (e.g., src/main/templates/scaffolding/index.gsp)

    • Used if no views exist at all

Example Use Case

This feature is useful when you want different scaffolded views for different namespaces:

// Regular event controller
@Scaffold(RestfulServiceController<Event>)
class EventController {
}

// Admin event controller with namespace
@Scaffold(RestfulServiceController<Event>)
class EventController {
    static namespace = 'admin'
}

With enableNamespaceViewDefaults: true, you can provide:

  • src/main/templates/scaffolding/index.gsp - Default scaffolded template

  • src/main/templates/scaffolding/admin/index.gsp - Admin-specific scaffolded template

The admin controller will use the admin-specific template even if a non-namespaced view exists.

Backward Compatibility

This feature is disabled by default (false), ensuring complete backward compatibility. Existing applications will continue to work without any changes. Enable the feature only when you need namespace-specific scaffolded template support.

2.6 Enhanced Display Constraint with DisplayType Enum

Grails 7.1 enhances the display constraint with a new DisplayType enum that provides fine-grained control over where properties appear in scaffolded views.

New DisplayType Values

The display constraint now accepts a DisplayType enum value in addition to boolean values:

Value Description

ALL

Display in all views. Overrides the default blacklist for properties like dateCreated and lastUpdated.

NONE

Never display in any view. Equivalent to display: false.

INPUT_ONLY

Display only in input views (create and edit forms).

OUTPUT_ONLY

Display only in output views (show and index/list views).

Example Usage
import static grails.gorm.validation.DisplayType.*

class Book {
    String title
    String isbn
    Date dateCreated
    Date lastUpdated
    String internalNotes

    static constraints = {
        dateCreated display: ALL         // Override blacklist, show in all views
        lastUpdated display: OUTPUT_ONLY // Show only in show/index views
        isbn display: INPUT_ONLY         // Show only in create/edit forms
        internalNotes display: NONE      // Never show
    }
}
Backward Compatibility

Boolean values continue to work as before:

  • display: true - Default behavior (property is displayed)

  • display: false - Equivalent to DisplayType.NONE

No changes are required for existing applications using boolean values.

2.7 @Scaffold Annotation (Preferred Approach)

Starting in Grails 7.1, the @Scaffold annotation is the preferred approach for enabling scaffolding on controllers and services. While the legacy static scaffold = Domain syntax is still supported, the annotation provides additional features and flexibility.

Benefits of @Scaffold
  • Supports both controllers and services - The annotation works on both artefact types

  • Custom class extension - Specify a custom class to extend using the generic syntax

  • Service-backed controllers - Create controllers that delegate to a service layer for better separation of concerns

  • Read-only mode - Built-in support for read-only scaffolding

Migration Examples

Basic controller scaffolding:

// Legacy syntax
class BookController {
    static scaffold = Book
}

// New preferred syntax
import grails.plugin.scaffolding.annotation.Scaffold

@Scaffold(Book)
class BookController {
}

Scaffolded service:

import grails.plugin.scaffolding.annotation.Scaffold

@Scaffold(Book)
class BookService {
}

Service-backed controller (delegates to BookService):

import grails.plugin.scaffolding.annotation.Scaffold
import grails.plugin.scaffolding.RestfulServiceController

@Scaffold(RestfulServiceController<Book>)
class BookController {
}

Custom class to extend:

import grails.plugin.scaffolding.annotation.Scaffold
import com.example.MyCustomService

@Scaffold(MyCustomService<Book>)
class BookService {
}

Read-only scaffolding:

import grails.plugin.scaffolding.annotation.Scaffold

@Scaffold(domain = Book, readOnly = true)
class BookController {
}
CLI Commands

New CLI commands are available to generate scaffolded controllers and services:

# Generate a scaffolded controller
grails create-scaffold-controller Book

# Generate a scaffolded service
grails create-scaffold-service Book

# Generate both service and controller
grails generate-scaffold-all Book

These commands support options like --extends to specify a custom class to extend, --namespace for controller namespacing, and --service to use RestfulServiceController.

For full details, see the Scaffolding documentation.

2.8 URL Mapping Group Defaults

The group method in URL mappings now accepts default parameters that are inherited by all child mappings:

group "/api", namespace: 'api', controller: 'resource', {
    "/list"(action: 'list')    // inherits namespace and controller
    "/show"(action: 'show')    // inherits namespace and controller
}

Nested groups merge defaults — inner groups inherit and can extend or override outer group defaults. Child mappings can override any default explicitly. This is a new additive feature and does not affect existing URL mappings.

For full details, see the Group Defaults documentation.

2.9 URL Mapping Wildcard Validation

Grails now validates wildcard-captured URL mapping variables ($controller, $action, $namespace) against registered controller artefacts at request time. When a captured value does not match a registered artefact, the mapping is skipped and the next mapping is tried.

This is a new behavior that is enabled by default. It allows patterns like:

group "/topics", {
    "/$action"(controller: 'topic')    // tried first — only matches real actions
    "/$id"(controller: 'topic', action: 'show')  // fallback for non-action values
}

Hardcoded controller/action values in mappings are not affected — only wildcard-captured values are validated.

If this causes issues with existing URL mappings, you can disable it in application.yml:

grails:
    urlmapping:
        validateWildcards: false
2.10 ContainerGebSpec Context Path Support

ContainerGebSpec now automatically includes the server’s servlet context path in the browser’s base URL. Previously, if your application configured a context path via server.servlet.context-path, the ContainerGebSpec base URL would only include the protocol, hostname, and port — causing all page navigations to miss the context path and result in 404 errors.

Starting in Grails 7.1, the context path is looked up from the Spring Environment at test setup time and appended to the base URL automatically. No changes are required in your test code — Geb page URLs should remain relative to the context root (e.g., static url = 'greeting'), and the framework handles prepending the context path.

Example
application.yml
server:
  servlet:
    context-path: /myapp
// Page URL is relative — no need to include /myapp
class GreetingPage extends Page {
    static url = 'greeting'
    static at = { title == 'Greeting' }
}

// Navigation automatically resolves to http://host:port/myapp/greeting
@Integration
class MySpec extends ContainerGebSpec {
    void 'should reach the greeting page'() {
        expect:
        to(GreetingPage)
    }
}

3.3 Upgrading from Grails 6 to Grails 7.0

To ensure compatibility with Grails 7.0.x, please review the following upgrade instructions carefully. This guide outlines the necessary steps & warnings to upgrade your Grails project.

1. Java 17 as baseline:

Starting from Grails 7, Java 17 serves as the baseline requirement for the framework, matching Spring Framework and Spring Boot’s minimum Java version. When upgrading to Grails 7, ensure that your project is configured to use Java 17. Updating to Java 17 allows you to take advantage of the latest features, security enhancements, and performance improvements provided by Java 17.

Please make sure to update your project’s Java version to 17 before proceeding with the Grails 7 upgrade. Doing so will ensure a seamless transition to the latest version of Grails and enable you to enjoy all the benefits that Java 17 has to offer.

2. Groovy 4.0.x as a baseline:

Grails 7.0.x adopts Groovy 4.0.30 as it’s baseline requirement. Several changes in Groovy 4 can affect your application. Changes that may affect a Grails Application or Plugin include:

  • GROOVY-10621 - Primitive booleans will no longer generate the form of isProperty & getProperty. They will only generate isProperty(). For Grails Domain objects, grails sometimes generates these properties. It is advisable to switch to the new form of the property.

  • GROOVY-5169 & GROOVY-10449 - Fields with a public modifier were not returned with MetaClassImpl#getProperties() in groovy 3, but are now.

  • Closures using the DELEGATE_FIRST strategy now behave differently. The resolution order in Groovy 3 used to be Delegate’s invokeMethod, Owner’s invokeMethod, Delegate’s invokeMissingMethod, Owner’s invokeMissingMethod. In Groovy 4, the order is now Delegate’s invokeMethod, Delegate’s invokeMissingMethod, Owner’s invokeMethod, Owner’s invokeMissingMethod.

  • Closures defined in a parent class that reference its private properties or methods may no longer have access to them in subclasses. See GROOVY-11568 for more details.

  • Some older libraries may include an older version of groovy, but still be compatible with Groovy 4. One example is GPars. In your gradle file, you can force a dependency upgrade via this code:

build.gradle
  configurations.configureEach {
      resolutionStrategy.eachDependency { DependencyResolveDetails details ->
          if (details.requested.group == 'org.codehaus.groovy') {
              details.useTarget(group: 'org.apache.groovy', name: details.requested.name, version: '{groovyVersion}')
          }
      }
  }
  • By default, Groovy 4 switches away from callsite optimizations and uses invokedynamic instead. This can result in performance regressions compared to Grails 6. The Grails Gradle Plugins now automatically disables invokedynamic for all GroovyCompile tasks (see #15293). If you have a manual tasks.withType(GroovyCompile) block in your build.gradle that sets indy = false, you can safely remove it:

build.gradle - remove this block (no longer needed)
  // This is now handled by the Grails Gradle Plugin - remove it
  tasks.withType(GroovyCompile).configureEach {
        groovyOptions.optimizationOptions.indy = false
  }

To re-enable invokedynamic, use the grails extension in your build.gradle:

build.gradle
  grails {
      indy = true
  }

3. Unified Project Version

Grails 7 moved to a mono repository. As part of this move, all projects that make up a grails release now have a common version. Setting grailsVersion in gradle.properties is the proper way to specify a set of dependencies that are compatible with each other for a given Grails release. Please remove older properties such as gormVersion & grailsGradlePluginVersion and exclusively use this property going forward.

gradle.properties
grailsVersion=8.0.0-SNAPSHOT

4. Unified Bill of Materials

Previously Grails did not have a single Bill of Materials (BOM). Instead it had a micronaut-bom and a spring-bom that existed side by side. As of Grails 7, a grails-bom is published that inherits from the spring-bom and the micronaut-bom is not included by default.

Grails 7 introduces a BOM that includes all the dependencies required for a Grails application. This BOM is available in the org.apache.grails:grails-bom artifact. The BOM is automatically applied when a Grails Gradle plugin is applied via the Spring Dependency Management plugin. For projects that do not use a Grails Gradle plugin, you can apply the BOM via a Gradle Platform:

build.gradle
dependencies {
    implementation platform("org.apache.grails:grails-bom:{GrailsVersion}")
}

Because all Grails projects will apply the Grails Gradle plugin, the BOM is automatically applied. You can override a version in the BOM by setting the associated Gradle property. The possible gradle properties are generated as part of this documentation. Please see link:../ref/Versions/Grails BOM.html[Grails BOM Dependencies] for a quick reference.

4.1 BOM Property Changes

The initial Grails 7.0.0 release had an issue where properties in the bom were not correctly populated. The 7.0.4 release restored properties, but the naming was inconsistent and duplicated. Later versions of Grails 7 reworked the properties for the following reasons:

  • to follow previous naming strategies used by Grails 6 and earlier.

  • to remove duplicate properties.

  • to clearly delineate between profile dependencies.

  • to remove non-published projects.

  • to fix invalid versions for properties used by asciidoctor & rxjava.

Detailed differences follow in subsequent sections.

4.2 Property Name Standardization

In the prior 7.0.0 Grails versions, there were numerous duplicated properties for third-party dependencies, with one variant using hyphens (e.g., byte-buddy.version) and an identical one using dots (e.g., byte.buddy.version), both set to the same value. This applies to properties like:

asset-pipeline-gradle.version / asset.pipeline.gradle.version
byte-buddy.version / byte.buddy.version
commons-text.version / commons.text.version
directory-watcher.version / directory.watcher.version
grails-publish-plugin.version / grails.publish.version (though this is slightly inconsistent in naming)
javaparser-core.version / javaparser.core.version
bootstrap-icons.version / bootstrap.icons.version (literal duplicate entries)
commons-codec.version / commons.codec.version
geb-spock.version / geb.spock.version
asset-pipeline-bom.version / asset.pipeline.bom.version
spock.version / spock.bom.version (values match but names differ slightly)
jackson.version / jackson.bom.version
groovy.version / groovy.bom.version
selenium.version / selenium.bom.version

To resolve this duplication and standardize naming conventions, the Grails team has consolidated these properties. All dotted variants have been removed. Only the hyphenated forms are retained (e.g., byte-buddy.version, asset-pipeline-gradle.version).

Additionally, Grails-specific properties in earlier Grails 7 versions used dots in their names (e.g., grails.async.version, grails.data.hibernate5.version). These have been refactored to use hyphens instead (e.g., grails-async.version, grails-data-hibernate5.version). This aligns them with the third-party property naming convention.

These changes eliminate redundancy and enforce consistency across the Grails bom & the boms it inherits.

4.3 Profile Properties are renamed

In previous Grails 7 versions, profile-specific properties were not prefixed with grails-profile. These have been corrected as follows:

base.version - grails-profile-base.version
plugin.version - grails-profile-plugin.version
profile.version - grails-profile-profile.version
rest.api.version - grails-profile-rest-api.version
rest.api.plugin.version - grails-profile-rest-api-plugin.version
web.version - grails-profile-web.version
web.plugin.version - grails-profile-web-plugin.version
4.4 Removal of Redundant or Unused Properties

Several groups of related properties have been consolidated or removed in later Grails 7 versions:

  • Liquibase: (liquibase-hibernate5.version, liquibase.version, liquibase.cdi.version, liquibase.core.version, liquibase5.hibernate.version) have been replaced by liquibase-hibernate5.version

  • MongoDB: (mongodb.version, bson.version, mongodb.driver.core.version, mongodb.driver.sync.version, bson.record.codec.version) have been replaced by mongodb.version

  • Sitemesh: (starter-sitemesh.version, spring.boot.starter.sitemesh.version) have been consolidated to starter-sitemesh.version

  • Ant: (ant.version and ant.junit.version) have been consolidated to ant.version

  • Spring Boot sub-components: (spring.boot.cli.version and spring.boot.gradle.plugin.version) have been consolidated to use spring-boot.version

  • Asciidoctor Gradle: asciidoctor.gradle.jvm.version was renamed to asciidoctor-gradle-jvm.version

  • Spring Boot dependencies: spring.boot.dependencies.version was renamed to spring-boot.version

  • Liquibase Hibernate: liquibase.hibernate5.version & liquibase-hibernate5.version were consolidated to liquibase-hibernate5.version

4.5 Specific Property Value Changes

rxjava properties were incorrect previously, they are now fixed per the below:

  • rxjava.version (for RxJava 1.x, groupId io.reactivex) was set to 3.1.11 instead of 1.3.8

  • rxjava2.version (for RxJava 2.x, groupId io.reactivex.rxjava2) was set to 3.1.11 instead of 2.2.21

  • rxjava3.version (for RxJava 3.x, groupId io.reactivex.rxjava3) was set to 3.1.11 and remains 3.1.11

4.6 Additions and Refinements in <dependencyManagement>

Several boms are imported and previously used customized property names for them. They now align with a single property name. Most importantly, spring-boot-dependencies now uses ${spring-boot.version} (aligning with the removal of spring.boot.dependencies.version).

All published Grails projects continue to have separate properties to allow for overriding their versions individually.

5. Coordinate Changes

Grails has transitioned to the Apache Software Foundation (ASF). As a result, the group ID for Grails artifacts has changed to org.apache.grails. This change applies to all Grails packages, including core plugins and core dependencies, maintained by the Grails team. When upgrading to Grails 7, ensure that you update your build configuration to use the new group ID.

There is a RENAME.md in the grails-core repository that maps previous coordinate names to new names. Additionally, there is a script to aid in the updating of Grails projects to the new coordinates. The script is located in the etc/bin directory of the grails-core repository and can be run as follows:

  ./rename_gradle_artifacts.sh -l my/project/location

This script will scan any gradle file and attempt to update both exclusions & dependencies to the new coordinates.

6. Gradle Changes:

One of the major changes in Grails 7 is the modernization of it’s build system. As a side effect of this modernization, the Grails build is fully parallel & lazy. Moreover, most tasks have been enhanced to be cacheable. When updating to Grails 8.0.0-SNAPSHOT, there may be required updates to various tasks we distribute since the inputs of those tasks have changed to support lazy configuration.

6.1. Gradle Plugin Changes

As part of the cacheable task changes, some tasks have slightly new behaviors:

  • FindMainTask can now fail for non-plugins: If an Application class is not found & the project has a Grails gradle plugin applied, the FindMainTask will now fail.

  • FindMainTask is now cacheable - restarting with bootRun should not force a recompile of the application if no changes have been made.

  • Configuration tasks have been added to customize compileGroovy tasks. These tasks allow for proper metadata being passed to the Groovy compiler to ensure the AST transformations know if code is a Grails Plugin or a Grails Application. In the event these tasks do not run or are caching incorrectly, you may observe incorrect UrlMapping resolution. Please report any instances of this happening so it can be addressed.

  • groovyVersion is now groovy.version to match upstream Spring BOM property names. If you still define groovyVersion to override the groovy version & use the Spring Dependency Management plugin, you will need to change this property to groovy.version in your gradle.properties file.

  • While the Spring Dependency Management plugin is still used by default, it can now be disabled by setting the property springDependencyManagement on the grails extension.

6.2. Upgrading Gradle:

It is recommended to set your gradle version to a version of 8.14.4 or higher:

./gradlew wrapper --gradle-version 8.14.4

This command will download the specified Gradle version and update the Gradle wrapper settings in your project.

6.3. Check Gradle Version:

After the command finishes, you can verify that the Gradle version has been updated by checking the gradle-wrapper.properties file located in the gradle/wrapper directory. The distributionUrl in the file should now point to the specified Gradle version’s distribution:

distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip
6.4. Removed Gradle Plugins

The grails-doc plugin has been removed. Documentation generation is no longer provided by Grails.

7. Reproducible Builds:

The ASF strongly encourages reproducible builds as part of it’s security requirements. We have begun making the Grails build reproducible - that is, if you build with the same settings as the GitHub action the produced artifacts should be identical. We have starting addressing reproducible builds under the ticket #14679.

A side effect of making our builds reproducible is that Grails applications can also now be reproducible. The benefits of reproducible builds are well documented, but for Grails applications, some of the side effects include:

  1. Improved build times - since the build is reproducible, Gradle can cache the results of the build and reuse them in future builds.

  2. Minimizing rebuilds - if the build is reproducible, Gradle can avoid unnecessary rebuilds of tasks that have not changed.

  3. Improved security - reproducible builds can help to ensure that the build process is not tampered with, and that the resulting artifacts are exactly what was intended.

To make your local build reproducible, you’ll want to set the environment variable SOURCE_DATE_EPOCH to a fixed date. For Grails, we set this value to the last commit in git via the bash command: SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)

8. Spring 3.5.x:

Grails 7.0.x is built on the Spring Framework, version 6.2.x. If your project uses Spring-specific features, refer to the Upgrading to Spring 6 guide.

This update introduces enhancements and fixes to the Spring Framework, providing you with the latest improvements in dependency injection, web frameworks, and other Spring-related functionalities.

9. Spring Boot 6.2.x:

Grails 6 used Spring Boot 2.7.x. Grails 7.0.x updates this to Spring Boot 3.5.x. For more information, consult the release notes for previous Spring Boot releases: 1. Spring Boot 3.5 Release Notes 2. Spring Boot 3.4 Release Notes 3. Spring Boot 3.3 Release Notes 4. Spring Boot 3.2 Release Notes 5. Spring Boot 3.1 Release Notes 6. Spring Boot 3.0 Release Notes

10. Upgrading Your Project:

In addition to the coordinate changes, the version updates, and the build updates, to upgrade your project it is advised to use an application generator and compare it to your existing project. The preferred app generator for Grails is start.grails.org. Please note that several previously required gradle configurations have been integrated into the Grails Gradle Plugins as defaults. Compare a freshly generated application to your application and apply any associated changes.

10.1 Building the Project:

After comparing to a generated build & updating your gradle files, you can now build your Grails project using the updated Gradle version:

./gradlew build

This will initiate the build process with the new Gradle version.

11. Grails CLI

Both the legacy grails-shell-cli and grails-forge-cli are now included with Grails 7. Please see the Downloading & Installing section of this guide for important background on how to use the Grails CLI commands.

12. Breaking changes

Grails 7 introduces several breaking changes that may require updates to your application.

12.1

Due to the significant changes in Grails 7, all prior Grails Plugins will need updated to work with Grails 7. For plugins in the Grails Plugins GitHub organization, the Grails Core team is updating them as time permits and when requested. If there is a plugin that you require updating, please reach out via ticket to see if we can help.

12.2 javax → Jakarta

Spring has switched from javax to jakarta packages. Please consult the Spring upgrade guides for the impacts of this change.

Gradle Javax-to-Jakarta EE migration plugins, such as the gradle-jakartaee-migration-plugin, provide transforms and dependency substitutions to ease the migration from Java EE (javax.) to Jakarta EE (jakarta.). These plugins can help mitigate upgrade challenges with older dependencies that still use javax. until they are updated or replaced with alternative solutions.

To apply and configure the gradle-jakartaee-migration-plugin in your build.gradle, you can use the following snippet:

build.gradle
plugins {
    id 'com.netflix.nebula.jakartaee-migration'  version '1.0.0'
}

jakartaeeMigration {
    // Exclude Grails dependencies since AST and jakartaeeMigration are not compatible
    excludeTransform('org.apache.grails:**')
    excludeTransform('org.apache.grails.**:**')

    // Exclude logback-core to avoid extra logback debug messages
    excludeTransform('ch.qos.logback:logback-core')

    // enable automatic migration from EE 8 (javax) or earlier to EE 10 (jakarta) or later
    migrate()
}
12.3 Removed libraries/classes
  • The grails-web-fileupload library, including its sole class ContentLengthAwareCommonsMultipartResolver, has been removed. This change was necessitated by the removal of the superclass CommonsMultipartResolver in Spring 6. The ContentLengthAwareCommonsMultipartResolver was originally introduced to address a bug in Safari back in 2007, but it is likely no longer needed. Spring has transitioned away from CommonsMultipartResolver and now recommends using the built-in support for multipart uploads provided by servlet containers. For more information on handling file uploads in Spring Boot, please refer to the relevant sections of the Spring Boot documentation and the Spring Framework 6 upgrade guide.

  • Removed deprecations:

    1. org.grails.spring.beans.factory.OptimizedAutowireCapableBeanFactory

    2. GrailsPlugin#checkForChangesExpected

    3. GrailsClassUtils#isGetter(String, Class[]) - use isGetter(String, Class, Class[]) instead.

    4. GrailsClassUtils#getPropertyForGetter(String) - use getPropertyForGetter(String, Class) instead.

    5. AbstractGrailsClass#getPropertyDescriptors - use getMetaProperties instead.

    6. org.grails.core.metaclass.BaseApiProvider - use traits instead.

    7. Several static variables on ClosureEventTriggeringInterceptor - use the variables on AbstractPersistentEvent instead.

    8. grails.testing.gorm.DataTest#dataStore - use DataTest#datastore instead.

  • The following deprecated classes were removed, please use the suggested replacement:

    1. grails.core.GrailsTagLibClassgrails.core.gsp.GrailsTagLibClass

    2. org.grails.core.artefact.TagLibArtefactHandlerorg.grails.core.artefact.gsp.TagLibArtefactHandler

    3. org.grails.core.DefaultGrailsTagLibClassorg.grails.core.gsp.DefaultGrailsTagLibClass

    4. org.grails.plugins.CodecsGrailsPluginorg.grails.plugins.codecs.CodecsGrailsPlugin

    5. grails.artefact.AsyncControllergrails.async.web.AsyncController

    6. grails.beans.util.LazyBeanMapgrails.beans.util.LazyMetaPropertyMap

    7. org.grails.plugins.databinding.DataBindingGrailsPluginDataBindingConfiguration

12.4 Micronaut in Grails is now supported via the plugin grails-micronaut

In Grails 4 to Grails 6, Micronaut was integrated by making Micronaut the parent context of the Grails application. As of Grails 7, Micronaut is set up via the micronaut-spring-starter using the grails-micronaut plugin. Discussion around this change can be found here.

To enable Micronaut in your Grails application, two steps must be completed. First, the grails-micronaut plugin needs added to the build file. Second, the property micronautPlatformVersion needs set to your desired version. Only Micronaut 4.9.2 or higher is supported for Grails.

Here’s an example build file:

build.gradle
dependencies {
    implementation 'org.apache.grails:grails-micronaut'
}

Here’s an example gradle.properties file:

gradle.properties
micronautPlatformVersion=4.9.2

Please note that, due to this issue, Spring Boot DevTools does not work with the Micronaut integration.

The Grails Gradle Plugin automatically configures Groovy-based Micronaut bean registration via AST transforms (micronaut-inject-groovy on compileOnlyApi). Groovy classes annotated with @Singleton, @Factory, @ConfigurationProperties, etc. are processed automatically.

If your project contains Java source files with Micronaut annotations (e.g. @Singleton, @Factory), you must manually add the Micronaut annotation processor to your build.gradle. The annotation processor is not configured automatically because it is incompatible with Groovy incremental compilation (see #15211). For projects that mix Java and Groovy Micronaut beans, consider splitting them into separate source sets or modules to avoid incremental compilation issues.
build.gradle - Adding annotation processor for Java Micronaut beans
dependencies {
    annotationProcessor platform("io.micronaut.platform:micronaut-platform:$micronautPlatformVersion")
    annotationProcessor 'io.micronaut:micronaut-inject-java'
    annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
}
The Grails Gradle Plugin automatically configures the Spring Boot bootJar and bootWar tasks to use the CLASSIC loader implementation when grails-micronaut is detected. This is required for java -jar execution to work correctly with the Micronaut-Spring integration (see #15207). If you have explicitly set loaderImplementation in your build.gradle, you can remove it as the plugin now handles this automatically.
12.5 hibernate-ehcache

The org.hibernate:hibernate-ehcache library is no longer provided by the org.apache.grails:grails-hibernate5 plugin. If your application depends on hibernate-ehcache, you must now add it explicitly to your project dependencies.

Since hibernate-ehcache brings in a conflicting javax version of org.hibernate:hibernate-core, it is recommended to exclude hibernate-core from the hibernate-ehcache dependency to avoid conflicts:

build.gradle
dependencies {
    implementation 'org.hibernate:hibernate-ehcache:5.6.15.Final', {
        // exclude javax variant of hibernate-core
        exclude group: 'org.hibernate', module: 'hibernate-core'
    }
    runtimeOnly 'org.jboss.spec.javax.transaction:jboss-transaction-api_1.3_spec:2.0.0.Final', {
        // required for hibernate-ehcache to work with javax variant of hibernate-core excluded
    }
}
12.6 H2

The test database H2 is stricter about reserved keywords. If you use H2 in your application, please take a look at this pull request for examples of these new restrictions.

12.7 Removal of Test Dependencies from Production Classpath

Prior versions of Grails included test dependencies on the production classpath. These are now removed in Grails 7. If you still need them, you can add them to your implementation configuration in your build.gradle file.

12.8 Jar Artifact name changes

Jar artifacts produced by Grails Plugins will no longer have the suffix -plain. Please see ticket #347 for details.

12.9 Java 20+ Date Formatting Changes

In Java 20+, Unicode CLDR42 was implemented which changed the space character preceding the period (AM or PM) in formatted date/time text from a standard space (" ") to a narrow non-breaking space (NNBSP: "\u202F"). Additionally, when using the LONG or FULL timeStyle with dateStyle, the date and time separator has changed from ' at ' to ', '. IE. January 5, 1941, 8:00:00 AM UTC vs. January 5, 1941 at 8:00:00 AM UTC

12.10 Container runtime environment is now required for standard Geb functional and integration tests

The Grails Geb Plugin has received a significant update, introducing test fixtures that enable ubiquitous containerized browser testing.

This new approach is now the recommended way to write functional tests in Grails, but it requires a container runtime environment.

The previous method using WebDriver binaries remains supported for backward compatibility, although matching driver and browser versions can be challenging.

Key Features

By extending your test classes with ContainerGebSpec, your tests will automatically leverage a containerized browser provided by Testcontainers. This setup eliminates the need for managing browser versions and ensures consistent test environments.

Requirements

To use ContainerGebSpec, ensure that you have a compatible container runtime installed. Supported options include:

  • Docker Desktop

  • OrbStack (macOS only)

  • Rancher Desktop

  • Podman Desktop

  • Colima (macOS and Linux)

How It Works

Once a compatible container runtime is installed, no additional configuration is needed. Simply extend your test classes with ContainerGebSpec (instead of GebSpec), and the following will happen:

  1. A container will be started automatically when you run your integration tests.

  2. The container will be configured to launch a browser capable of accessing your application under test.

With this setup, you gain the benefits of containerized testing, such as isolation, reproducibility, and reduced setup complexity.

12.11 Asset Pipeline

The asset pipeline has a new home. Version 5.0.12 and forward is compatible with Grails 7.x.

Updated maven coordinates:

build.gradle
cloud.wondrify:asset-pipeline-gradle
cloud.wondrify:asset-pipeline-grails

Gradle plugin:

build.gradle
plugins {
    id "cloud.wondrify.asset-pipeline"
}

or

apply plugin: "cloud.wondrify.asset-pipeline"
12.12 API Changes

As part of any major release, APIs can change. For Grails 7.0, the list of renamed APIs follows:

  • GrailsClassUtils#isMatchBetweenPrimativeAndWrapperTypes → GrailsClassUtils#isMatchBetweenPrimitiveAndWrapperTypes

12.13 Layout Plugins

Grails 7 has two different layout engines: Sitemesh 2.6.x & Sitemesh 3.x. The Sitemesh2 plugin is named grails-layout and the Sitemesh 3 plugin is grails-sitemesh3. The grails-layout plugin is what has traditionally shipped with Grails. The grails-sitemesh3 plugin is functional, but has some known issues. Please see the thread Grails 7 & Reverting Sitemesh 3 for the history of why we did not exclusively use grails-sitemesh3 for Grails 7.

12.14 grails-layout Configuration

If you decide to use the grails-layout plugin, several changes have occurred:

Add the following dependency to your project:

build.gradle
implementation "org.apache.grails:grails-layout"

Package Changes:

  • grails.web.sitemesh → org.apache.grails.web.layout

  • org.grails.web.sitemesh → org.apache.grails.views.gsp.layout

Notable Class Moves:

  • org.grails.web.servlet.view.GrailsLayoutViewResolver → org.apache.grails.web.layout.EmbeddedGrailsLayoutViewResolver

  • org.grails.web.servlet.view.SitemeshLayoutViewResolver → org.apache.grails.web.layout.GrailsLayoutViewResolver

  • org.grails.web.sitemesh.GrailsLayoutView → org.apache.grails.web.layout.EmbeddedGrailsLayoutView

  • org.grails.web.sitemesh.SitemeshLayoutView → org.apache.grails.web.layout.GrailsLayoutView

  • org.grails.web.sitemesh.GSPSitemeshPage → org.apache.grails.web.layout.GSPGrailsLayoutPage

Property Changes:

  • grails.sitemesh.default.layout → grails.views.layout.default

  • grails.sitemesh.enable.nongsp → grails.views.layout.enable.nongsp

  • org.grails.web.sitemesh.GrailsLayoutView.GSP_SITEMESH_PAGE → org.apache.grails.web.layout.EmbeddedGrailsLayoutView.GSP_GRAILS_LAYOUT_PAGE

Tag namespace changes:

  • sitemesh → grailsLayout

12.15 Tomcat 10.1.42 introduced limits for part count and header size in multipart/form-data requests

These limits can be customized using server.tomcat.max-part-count and server.tomcat.max-part-header-size respectively.

server.tomcat.max-part-count default is 10 server.tomcat.max-part-header-size defalt is 512B

12.16 servletContext no longer included by default in generated Bootstrap init{}

The servletContext is no longer included by default in the generated Bootstrap class. If you need access to the servletContext, you can inject it into your Bootstrap class using:

ServletContext servletContext
12.17 Development Reloading

For Grails 7, as was the case with Grails 4 and 5 and shell-generated applications in 6, Spring Boot Developer Tools is used by default for development reloading within generated applications.

To learn more about development reloading options, please see the Development Reloading section of this guide.

12.18 grails-i18n plugin

org.apache.grails:grails-i18n has been changed to org.apache.grails.i18n:grails-i18n and is provided transitively, remove org.apache.grails:grails-i18n and org.grails:grails-plugin-i18n from your dependency list

12.19 hibernate.cache.region.factory_class

org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory is deprecated in Hibernate 5.6 and is not compatible with org.hibernate:hibernate-core-jakarta:5.6.15.Final, which is used in Grails 7.

application.yml
    hibernate:
        allow_update_outside_transaction: true
    cache:
        queries: false
        use_second_level_cache: true
        use_query_cache: false
        region:
        factory_class: 'org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory'

If your application sets hibernate.cache.region.factory_class to org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory it will add the non-jakarta version of hibernate-core to your classpath which will cause NoClassDefFoundError and ClassNotFoundException errors.

You will need to change it to jcache and add the Ehcache dependency as follows:

application.yml
    hibernate:
        allow_update_outside_transaction: true
    cache:
        queries: false
        use_second_level_cache: true
        use_query_cache: false
        region:
            factory_class: 'jcache'
build.gradle
    implementation 'org.ehcache:ehcache', {
        capabilities {
            requireCapability('org.ehcache:ehcache-jakarta')
        }
    }

Alternatively, you can define the hibernate-ehcache dependency explicitly and adjust it to exclude hibernate-core and add the jboss-transaction-api_1.3_spec see Hibernate-ehcache

12.20 A Grails project cannot be both a plugin & an Application

In previous versions of Grails, it was possible to apply both the grails-plugin and grails-web Gradle plugins in build.gradle. This would force the project to be both a Grails Plugin and a Grails Application. This scenario is not supported and can lead to unexpected behavior due to the AST transforms.

Starting with Grails 7, a validation error will trigger if both a Grails Application Gradle Plugin & a Grails Plugin Gradle Plugin are configured in build.gradle for the same Gradle Project.

12.21 exploded is supported again to enable multi-project reloading

Earlier versions of Grails supported an exploded Gradle configuration that forced defined plugins to use class and resource files, instead of jar files, on the Grails Application runtime classpath if several conditions were true:

  1. the property grails.run.active was set

  2. the plugin project had the property exploded set to true prior to the application of the grails-plugin Gradle plugin

  3. plugins were added to the Grails Application project via the plugins block inside of the grails extension in build.gradle instead of the dependencies block

  4. the property exploded was set on the grails extension in build.gradle of the Grails Application project

The exploded setup facilitates better class reloading behavior. For Grails 7, it has been simplified to the following:

  1. In the plugin project, apply the gradle plugin org.apache.grails.gradle.grails-exploded

  2. In the application project, define plugins inside of the plugins block of the grails extension in build.gradle:

grails {
   plugins {
        implementation project(":my-plugin")
   }
}
Expanded class files & resource files will only be used over the jar file if the plugin applies the gradle plugin org.apache.grails.gradle.grails-exploded & the plugin is a subproject of your Gradle build.
12.22 Database Migration Plugin migrations directory in the main sourceSet

It is no longer necessary to add the grails-app/migrations directory to the main sourceSet in order for the Database Migration Plugin to find your changelogs. This is now done automatically by the org.apache.grails.gradle.grails-plugin Gradle plugin.

The following code can be removed from your build.gradle file:

build.gradle
sourceSets {
    main {
        resources {
            srcDir 'grails-app/migrations'
        }
    }
}
12.23 Test & JavaExec JVM Args no longer set by default

Previously tasks of type Test & JavaExec had several JVM args set by default to facilitate faster startup times in larger projects. These settings are removed by default in Grails 7. To restore the old behavior, configure the Test & JavaExec tasks as follows:

tasks.withType(Test).configureEach {
   jvmArgs('-XX:+TieredCompilation', '-XX:TieredStopAtLevel=1', '-XX:CICompilerCount=3')
}
tasks.withType(JavaExec).configureEach {
   jvmArgs('-XX:+TieredCompilation', '-XX:TieredStopAtLevel=1', '-XX:CICompilerCount=3')
}
12.24 MongoDB

Instant persistence switched from epoch milliseconds (BSON int64) to BSON DateTime (same BSON type as java.util.Date); LocalDateTime continues to be converted to BSON DateTime using the configured zone. If you currently using domain objects with property type Instant, you will need to convert them prior to upgrading. For more info, see #15111.

db.Example.updateMany(
  { created: { $type: "long" } },
  [ { $set: { created: { $toDate: "$created" } } } ]
);
12.25 JSON Rendering of Date/Time Types

JSON rendering of java.util.Calendar, java.time.Instant, java.time.LocalDate, java.time.LocalDateTime, java.time.OffsetDateTime, and java.time.ZonedDateTime has been updated for consistency across both standard JSON converters (render …​ as JSON) and JSON views (.gson files).

Changes to JSON Output

Calendar: Previously rendered as a complex object with all properties. Now consistently renders as ISO-8601 format with Z suffix:

// Before
{"timestamp": {"time": 1759869218602, "timeZone": {...}, "firstDayOfWeek": 1, ...}}

// After
{"timestamp": "2025-10-07T21:14:31Z"}

Instant: Previously rendered as either epoch milliseconds (standard converters) or a complex object structure (JSON views). Now consistently renders as ISO-8601 format with Z suffix:

// Before (standard converters)
{"timestamp": 1759869218602}

// Before (JSON views)
{"timestamp": {"epochSecond": 1759869218, "nano": 602000000}}

// After (both)
{"timestamp": "2025-10-07T21:14:31.602Z"}

LocalDate: Previously rendered as a complex object structure. Now consistently renders as ISO-8601 date format (YYYY-MM-DD):

// Before
{"date": {"year": 2025, "month": "OCTOBER", "dayOfMonth": 8, ...}}

// After
{"date": "2025-10-08"}

LocalDateTime: Previously rendered as a complex object structure (JSON views) or inconsistently. Now consistently renders as ISO-8601 format without timezone (matching Spring Boot behavior):

// Before (JSON views)
{"dateTime": {"year": 2025, "month": "OCTOBER", "dayOfMonth": 7, ...}}

// After (both)
{"dateTime": "2025-10-07T21:14:31"}
LocalDate and LocalDateTime do not include timezone information, so they render without the Z suffix, unlike Date, Calendar, and Instant which represent specific points in time.

OffsetDateTime: Previously rendered as a complex object structure. Now consistently renders as ISO-8601 format with timezone offset:

// Before
{"dateTime": {"offset": {...}, "year": 2025, "month": "OCTOBER", ...}}

// After
{"dateTime": "2025-10-08T00:48:46.407254-07:00"}

ZonedDateTime: Previously rendered with zone ID brackets like [America/Los_Angeles]. Now consistently renders as ISO-8601 format with timezone offset only (matching Spring Boot):

// Before
{"dateTime": "2025-10-08T00:48:46.407254-07:00[America/Los_Angeles]"}

// After
{"dateTime": "2025-10-08T00:48:46.407254-07:00"}

java.util.Date: Now renders as ISO-8601 with Z suffix including milliseconds:

// Before
{"created": "2025-10-07T21:14:31Z"}

// After
{"created": "2025-10-07T21:14:31.407Z"}
java.util.Date and Calendar have millisecond precision (3 decimal places: .SSS), while Java 8 date/time types (Instant, OffsetDateTime, ZonedDateTime) have nanosecond precision (up to 9 decimal places, with trailing zeros dropped per ISO-8601 spec). This matches Spring Boot’s Jackson serialization behavior.
Migration Impact

If your application or API consumers depend on the previous JSON format for Calendar, Instant, LocalDate, LocalDateTime, OffsetDateTime, or ZonedDateTime fields:

  1. API Responses: Client applications may need updates to parse the new ISO-8601 string format instead of epoch milliseconds or object structures.

  2. Date Parsing: The new format is a standard ISO-8601 string that can be parsed using Instant.parse(), LocalDate.parse(), LocalDateTime.parse(), OffsetDateTime.parse(), or ZonedDateTime.parse() with appropriate formatters.

  3. Consistency:

    • Temporal types with timezone information (Date, Calendar, Instant) render with Z suffix (UTC)

    • LocalDate (date only) renders as YYYY-MM-DD

    • LocalDateTime (date and time, no timezone) renders without Z suffix

    • OffsetDateTime and ZonedDateTime render with their timezone offset (e.g., -07:00)

    • All formatting matches Spring Boot’s behavior

  4. ZonedDateTime: Note that the zone ID (e.g., [America/Los_Angeles]) is no longer included in the output, matching Spring Boot’s behavior.

This change applies to both the grails-converters module (standard JSON rendering) and the grails-views-gson module (JSON views).

12.26 Enum JSON/XML Serialization

As of Grails 7.0.2, enum serialization has been enhanced to support round-trip compatibility between JSON/XML serialization and deserialization. The legacy enum marshaller, which produced verbose output with type metadata, has been deprecated in favor of a simpler format that matches how enums are expected on input.

Legacy Behavior (Deprecated)

Previously, enums were serialized with metadata:

JSON:

{
  "stage": {
    "enumType": "com.example.ChallengeStage",
    "name": "SUBMIT"
  }
}

XML:

<stage enumType="com.example.ChallengeStage">SUBMIT</stage>

This format is asymmetric - when POSTing data, you send "stage":"SUBMIT", but when GETting data, you receive the verbose object structure.

The new SimpleEnumMarshaller serializes enums as simple string values, providing round-trip compatibility:

JSON:

{
  "stage": "SUBMIT"
}

XML:

<stage>SUBMIT</stage>

Now the format you POST is the same format you GET back.

Migration

To opt-in to the new behavior, add the following to your application.yml:

application.yml
grails:
  converters:
    json:
      enum:
        format: simple
    xml:
      enum:
        format: simple
Deprecation Timeline
  • 7.0.2: Legacy EnumMarshaller deprecated (default), SimpleEnumMarshaller available via config

  • 8.0: SimpleEnumMarshaller will become the default

The legacy org.grails.web.converters.marshaller.json.EnumMarshaller and org.grails.web.converters.marshaller.xml.EnumMarshaller classes are marked as @Deprecated(forRemoval = true, since = "7.0.2") and will be removed in Grails 8.0.

3.4 Upgrading from Grails 5 to Grails 6

To ensure compatibility with Grails 6, you must update the following versions in your project:

1. Java 11 as Baseline:

Starting from Grails 6, Java 11 serves as the baseline requirement for the framework. When upgrading to Grails 6, ensure that your project is configured to use Java 11. This compatibility with Java 11 allows you to take advantage of the latest features, security enhancements, and performance improvements provided by Java 11.

Please make sure to update your project’s Java version to 11 before proceeding with the Grails 6 upgrade. Doing so will ensure a seamless transition to the latest version of Grails and enable you to enjoy all the benefits that Java 11 has to offer.

2. The New Grails CLI:

Grails 6 comes with a completely revamped and highly efficient Command Line Interface (CLI) that enables you to generate applications and plugins at a remarkable speed. For instance, you can now use the new CLI to create a new Grails 6 application with the following command:

grails create-app my-app

The new CLI also allows you to generate plugins easily. For example, to create a new plugin named "my-plugin," you can use the following command:

grails create-plugin my-plugin

One notable improvement in Grails 6 is that it no longer supports certain commands that performed redundant tasks, such as the outdated grails run-app command. Instead, it recommends using the Gradle bootRun task for running your application, which offers better performance and functionality.

For example, to run your Grails 6 application, you can use the following command:

./gradlew bootRun

As a result of these improvements, the new CLI provides a more streamlined and efficient way to work with Grails applications and plugins.

Overall, Grails 6 offers a significantly improved development experience with its new CLI, optimized commands, and advanced features for generating applications and plugins.

3. Setting Grails Version and Grails Gradle Plugin:

To upgrade to Grails 6, it’s important to configure the appropriate versions in the gradle.properties file as shown below:

gradle.properties
grailsVersion=6.0.0
grailsGradlePluginVersion=6.0.0

By specifying the above versions, you’ll gain access to the latest features, improvements, and bug fixes introduced in Grails 6. Upgrading to this version empowers your application with enhanced performance and improved security. Additionally, it allows you to leverage the latest advancements in the Grails framework for a more efficient and secure development experience.

4. GORM Version:

If your project utilizes GORM, ensure to update the version in the gradle.properties file as demonstrated below:

gradle.properties
gormVersion=8.0.0

By upgrading to GORM 8.0.0, you will benefit from essential updates and optimizations. This upgrade guarantees seamless interactions with your database and enhances your data management experience. Staying current with GORM allows you to take advantage of the latest database features and improvements, thereby optimizing the performance and functionality of your application.

5. Gradle Version:

Grails 6 uses Gradle 7.6.2 which offers performance improvements, bug fixes, and new features over previous versions. Upgrading to the latest Gradle version helps accelerate your build processes and ensures compatibility with other dependencies.

5.1. Upgrade to Gradle 7.6.2

Run the following command to update the Gradle wrapper to the desired version (e.g., Gradle 7.6.2):

./gradlew wrapper --gradle-version 7.6.2

This command will download the specified Gradle version and update the Gradle wrapper settings in your project.

5.2. Check Gradle Version:

After the command finishes, you can verify that the Gradle version has been updated by checking the gradle-wrapper.properties file located in the gradle/wrapper directory. The distributionUrl in the file should now point to the Gradle 7.6.2 distribution:

distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
5.3. Build the Project:

After updating the Gradle wrapper, you can now build your Grails project using the updated Gradle version:

./gradlew build

This will initiate the build process with the new Gradle version.

6. Embracing Modern Plugin Management with Grails 6

In Gradle, there are two main ways to add plugins to your project: the plugins block and the apply plugin statement.

Grails 6 introduces a significant change in how plugins are managed by adopting the Gradle plugins block instead of the traditional apply plugin statements. This shift streamlines the project’s build configuration and brings it more in line with modern Gradle conventions. New Grails projects will now utilize the plugins block to manage plugin dependencies and configurations.

Using the plugins Block in Grails 6:

With the new approach, adding plugins to a Grails 6 project is more explicit and organized. In your build.gradle file, you can declare plugins within the plugins block, specifying the plugin’s ID and version.

Here’s an example of adding the views-json plugin using the plugins block:

build.gradle
plugins {
    id 'org.grails.plugins.views-json' version '3.0.0'
}

Managing Multiple Plugins:

The plugins block allows you to add multiple plugins, each on its own line. This enhances clarity and makes it easier to manage plugin dependencies.

build.gradle
plugins {
    id 'org.grails.plugins.views-json' version '3.0.0'
    // Add other plugins as needed
}

Moving Older Applications to the New Approach:

If you are migrating an older Grails application to Grails 6, you can update the plugin declarations from apply plugin to the plugins block. For example, if your previous application used the views-json plugin, you can modify the build.gradle as follows:

Before (Using apply plugin):

build.gradle
apply plugin: 'org.grails.plugins.views-json'

After (Using plugins Block in Grails 6):

build.gradle
plugins {
    id 'org.grails.plugins.views-json' version '3.0.0'
}

By migrating to the plugins block, your Grails 6 project will adhere to modern Gradle conventions, making it easier to manage plugin dependencies and configurations. This new approach maintains consistency and enhances the overall structure of the project, ensuring a smoother and more efficient development process.

7. GORM for MongoDB Sync Driver:

The GORM for MongoDB is updated to support the latest mongodb-driver-sync. If you are using GORM for MongoDB and making use of specific MongoDB Driver or low-level Mongo API features, consider checking the Upgrading to the 4.0 Driver guide.

This update ensures seamless integration with MongoDB, access to new features, and improved performance while interacting with your MongoDB database.

8. Asset Pipeline Plugin:

In Grails 6, there is an update to the Asset Pipeline Plugin, which is now version 4.3.0. The Asset Pipeline Plugin is a crucial component in Grails applications, responsible for managing frontend assets like stylesheets, JavaScript files, and images. The update to version 4.3.0 brings several improvements and new features to enhance the management and processing of frontend assets in your Grails projects.

The asset-pipeline plugin 4.3.0 offers new features for managing and processing your frontend assets, ensuring they are efficiently bundled and served to your users.

9. Spring 5.3:

Grails 6 is built on Spring 5.3.27. If your project uses Spring-specific features, refer to the Upgrading to Spring 5.3 guide.

Spring 5.3 introduces enhancements and fixes to the Spring framework, providing you with the latest improvements in dependency injection, web frameworks, and other Spring-related functionalities.

10. Spring Boot 2.7:

Grails 6 updates to Spring Boot 2.7. For more information, consult the Spring Boot 2.7 Release Notes

Spring Boot 2.7 comes with new features, performance enhancements, and compatibility improvements, making it a solid foundation for your Grails application.

11. Micronaut 3.9.3:

Grails 6 is shipped with Micronaut 3.9.3. If you are using specific Micronaut features, refer to the Upgrading to Micronaut 3.x guide.

Micronaut 3.9.3 brings new capabilities, improvements, and bug fixes, empowering your application with a powerful and lightweight microservices framework.

12. Micronaut for Spring 4.5.1:

Grails 6 is updated to use Micronaut for Spring 4.5.1. For more information, check out the release notes.

Micronaut for Spring 4.5.1 provides seamless integration between Micronaut and Spring, allowing you to leverage the strengths of both frameworks in your Grails project.

13. Upgrading the Spring Security Plugin

If your application uses the Grails Spring Security plugin, please consult the Upgrading from Previous Versions section of the Spring Security plugin documentation for specific upgrade instructions related to Grails 7.

3.5 Upgrading from Grails 4 to Grails 5

Bump up Grails Version

You will need to upgrade your Grails version defined in gradle.properties as:

gradle.properties
...
grailsVersion=5.2.0
...

Apache Groovy 3.0.7

Grails 5.1.1 provide support for Groovy 3. We would recommend you to please check the Release notes for Groovy 3 to update your application in case you are using a specific feature which might not work in Groovy 3.

Define groovyVersion in gradle.properties to force the application to use Groovy 3.

Grails 5.1 app’s gradle.properties

gradle.properties
...
groovyVersion=3.0.7
...

Bump up GORM Version

If you were using GORM, you will need to update the version defined in gradle.properties as:

gradle.properties
...
gormVersion=7.2.0
...

Bump up gradle version

Grails 5.2.x uses gradle 7.2

gradle-wrapper.properties
...
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
...

Also you can run this command

./gradlew wrapper --gradle-version 7.2

GORM for MonogDB Sync Driver

The GORM for MongoDB is updated to support latest mongodb-driver-sync. If you are using GORM for MongoDB and doing something specific to MongoDB Driver or low level Mongo API then you might want to take a look at Upgrading to the 4.0 Driver

Bump up Asset Pipeline plugin version

The previous version of asset-pipeline is not supported with Grails 5.0 as it is compiled with a version of Groovy which is binary incompatible with Groovy 3. So, please update the plugin version to 3.2.4.

Disabled StringCharArrayAccessor by default

The previous version of Grails use the StringCharArrayAccessor which is enabled by default and provides optimized access to java.lang.String internals. In Grails 5.0 it is disabled by default but you can enable it by setting a system property with name stringchararrayaccessor.disabled and value false.

Enabling StringCharArrayAccessor would show IllegalReflectiveAccess warnings as it uses reflection to do the optimizations.

Changes in profile.yml and feature.yml files in Grails Profiles

The format of how dependencies are defined in features and profiles has been changed. See the section on Application Profiles for more information.

Deprecation of dot navigation of Grails configuration

In order to reduce complexity, improve performance, and increase maintainability, accessing configuration through dot notation (config.a.b.c) has been deprecated. This functionality will be removed in a future release.

Also, you would see a warning message if you are accessing configuration through the dot notation.

The recommended way to access configuration is:

grailsApplication.config.getProperty("hola", String.class)

Spring 5.3

Grails 5.0.0.RC1 is built on Spring 5.3.2 See the Upgrading to Spring 5.3 if you are using Spring specific features.

Spring Boot 2.4

Grails 5.1.1 updates to Spring Boot 2.6. Please check Spring Boot 2.6 Release Notes for more information.

Micronaut 3.2.0

Grails 5.1.1 is shipped with Micronaut 3.2.0. Please check the Upgrading to Micronaut 3.x if you are using a specific feature.

Micronaut for Spring 4.0.1

Grails 5.1.1 is updated to Micronaut for Spring 4.0.1, please check out release notes for more information.

Gradle 7.x

Compile dependency configuration as well as others have been removed from Gradle 7.x. In previous version they were deprecated.

Replace configurations:

build.gradle
...
 compile -> implementation
 testCompile -> testImplementation
 runtime -> runtimeOnly
...
More information in Gradle upgrade docs Gradle upgrade docs

Plugins in multi-project setup

If you have grails plugins as part of multi-project builds you should also replace the compile with implementation configuration.

Additionally if your main application relied on the dependencies declared by the plugin you need to apply further changes.

To make the dependencies available again you have to declare them with api configuration. You also have to apply the java-library gradle plugin in your plugin project.

More information gradle java-library-plugin

3.6 Upgrading from Grails 3.3.x to Grails 4

Bump up Grails Version

You will need to upgrade your Grails version defined in gradle.properties.

Grails 3 app’s gradle.properties

gradle.properties
...
grailsVersion=3.3.8
...

Grails 4 app’s gradle.properties

gradle.properties
...
grailsVersion=4.0.4
...

Bump up GORM Version

If you were using GORM, you will need to update the version defined in gradle.properties.

Grails 3 app’s gradle.properties

gradle.properties
...
gormVersion=6.1.10.RELEASE
...

Grails 4 app’s gradle.properties

gradle.properties
...
gormVersion=7.0.4
...

Move GORM DSL Entries to runtime.groovy

GORM DSL entries should be move to runtime.groovy. For instance, using following GORM configuration in the application.groovy is not supported and will break the application:

grails.gorm.default.mapping = {
    id generator: 'identity'
}

Spring 5 and Spring Boot 2.1

Grails 4.0 is built on Spring 5 and Spring Boot 2.1. See the migration guide and release notes if you are using Spring specific features.

Hibernate 5.4 and GORM 7.x

Grails 4.x supports a minimum version of Hibernate 5.4 and GORM 7.x. Several changes have been made to GORM to support the newer version of Hibernate and simplify GORM itself.

The details of these changes are covered in the GORM upgrade documentation.

Spring Boot 2.1 Actuator

Please check the Spring Boot Actuator documentation since it has changed substantially from Spring Boot 1.5 the version Grails 3.x used.

If you had configuration such as:

grails-app/conf/application.yml - Grails 3.3.x
endpoints:
    enabled: false
    jmx:
        enabled: true
        unique-names: true

replace it with:

grails-app/conf/application.yml - Grails 4.x
spring:
    jmx:
        unique-names: true
management:
    endpoints:
        enabled-by-default: false

Spring Boot Developer Tools and Spring Loaded

Previous versions of Grails used a reloading agent called Spring Loaded. Since this library is no longer maintained and does not support Java 11 support for Spring Loaded has been removed.

As a replacement, Grails 4 applications include Spring Boot Developer Tools dependencies in the build.gradle build script. If you are migrating a Grails 3.x app, please include the following set of dependencies:

build.gradle
.
..
...
configurations {
    developmentOnly
    runtimeClasspath {
        extendsFrom developmentOnly
    }
}

dependencies {
        developmentOnly("org.springframework.boot:spring-boot-devtools")
        ...
        ..
}
...
..
.

Also you should configure the necessary excludes for Spring Developer Tools in application.yml:

spring:
    devtools:
        restart:
            exclude:
                - grails-app/views/**
                - grails-app/i18n/**
                - grails-app/conf/**

The above configuration prevents the server from restarting when views or message bundles are changed.

You can use Spring Developer Tools in combination with a browser extension such as the Chrome LiveReload extension to get automatic browser refresh when you change anything in your Grails application.

Spring Boot Gradle Plugin Changes

Grails 4 is built on top of Spring Boot 2.1. Grails 3 apps were built on top of Spring Boot 1.x.

Your Grails 3 app’s build.gradle may have such configuration:

build.gradle
bootRun {
    addResources = true
    ...
}

Grails 4 apps are built on top of Spring Boot 2.1. Starting from Spring Boot 2.0, the addResources property no longer exists. Instead, you need to set the sourceResources property to the source set that you want to use. Typically that’s sourceSets.main. This is described in the Spring Boot Gradle plugin’s documentation.

Your Grails 4 app’s build.gradle can be configured:

build.gradle
bootRun {
        sourceResources sourceSets.main
    ...
}

Building executable jars for Grails Plugins

The bootRepackage task has been replaced with bootJar and bootWar tasks for building executable jars and wars respectively. Both tasks extend their equivalent standard Gradle jar or war task, giving you access to all of the usual configuration options and behaviour.

If you had configuration such as:

build.gradle | Grails 3
// enable if you wish to package this plugin as a standalone application
bootRepackage.enabled = false

replace it with:

build.gradle | Grails 4
// enable if you wish to package this plugin as a standalone application
bootJar.enabled = false

Upgrading to Gradle 5

Grails 3 apps by default used Gradle 3.5. Grails 4 apps use Gradle 5.

To upgrade to Gradle 5 execute:

./gradlew wrapper --gradle-version 5.0

Due to changes in Gradle 5, transitive dependencies are no longer resolved for plugins. If your project makes use of a plugin that has transitive dependencies, you will need to add those explicitly to your build.gradle file.

If you customized your app’s build, other migrations may be necessary. Please check Gradle Upgrading your build documentation. Especially notice, that default Gradle daemon now starts with 512MB of heap instead of 1GB. Please check Default memory settings changed documentation.

Groovy language update to 2.5.6

Keep in mind, that with grails 4.0.x there is a minor groovy language upgrade (e.g. 3.3.9. used groovy 2.4.x), which requires a couple of changes, that are immediately obvious when trying to compile your source code. However there are also issues with changed implementations of core linkedlist functions! Check an overview of the breaking changes here: Breaking changes of Groovy 2.5

Removed date helper functions

Most common issue is that date util functions have been moved to individual project, e.g new Date().format("ddMMyyyy") no longer works without adding:

build.gradle
dependencies {
    implementation "org.codehaus.groovy:groovy-dateutil:3.0.4"
}

Changed linked list method implementations

Check whether you are using the groovy version of linkedlist implementations:

  • [].pop() - will no longer remove the last, but the first element of the list. Replace it with [].removeLast() is recommended.

  • [].push(..) - will no longer add to the end, but to the beginning of the list. Replace it with [].add(..) is recommended.

H2 Web Console

Spring Boot 2.1 includes native support for the H2 database web console. Since this is already included in Spring Boot the equivalent feature has been removed from Grails. The H2 console is therefore now available at /h2-console instead of the previous URI of /dbconsole. See Using H2’s Web Console in the Spring Boot documentation for more information.

Upgrade Hibernate

If you were using GORM for Hibernate implementation in your Grails 3 app, you will need to upgrade to Hibernate 5.4.

A Grails 3 build.gradle such as:

build.gradle
dependencies {
...
  implementation "org.grails.plugins:hibernate5"
  implementation "org.hibernate:hibernate-core:5.1.5.Final"
}

will be in Grails 4:

build.gradle
dependencies {
...
  implementation "org.grails.plugins:hibernate5"
  implementation "org.hibernate:hibernate-core:5.4.0.Final"
}

Migrating to Geb 2.3

Geb 1.1.x (a JDK 1.7 compatible version) was the version shipped by default with Grails 3. Grails 4 is no longer compatible with Java 1.7. You should migrate to Geb 2.3.

In Grails 3, if your build.gradle looks like:

build.gradle
dependencies {
 testCompile "org.grails.plugins:geb:1.1.2"
 testRuntime "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"
 testRuntime "net.sourceforge.htmlunit:htmlunit:2.18"
}

In Grails 4, you should replace it with:

build.gradle
buildscript {
    repositories {
       ...
    }
    dependencies {
        ...
        classpath "gradle.plugin.com.energizedwork.webdriver-binaries:webdriver-binaries-gradle-plugin:$webdriverBinariesVersion" (1)
    }
}
...
..

repositories {
  ...
}

apply plugin:"idea"
...
...
apply plugin:"com.energizedwork.webdriver-binaries" (1)


dependencies {
...
    testCompile "org.grails.plugins:geb" (4)
    testRuntime "org.seleniumhq.selenium:selenium-chrome-driver:$seleniumVersion"  (5)
    testRuntime "org.seleniumhq.selenium:selenium-firefox-driver:$seleniumVersion" (5)
    testRuntime "org.seleniumhq.selenium:selenium-safari-driver:$seleniumSafariDriverVersion" (5)

    testCompile "org.seleniumhq.selenium:selenium-remote-driver:$seleniumVersion" (5)
    testCompile "org.seleniumhq.selenium:selenium-api:$seleniumVersion" (5)
    testCompile "org.seleniumhq.selenium:selenium-support:$seleniumVersion" (5)
}

webdriverBinaries {
    chromedriver "$chromeDriverVersion" (2)
    geckodriver "$geckodriverVersion" (3)
}

tasks.withType(Test) {
    systemProperty "geb.env", System.getProperty('geb.env')
    systemProperty "geb.build.reportsDir", reporting.file("geb/integrationTest")
    systemProperty "webdriver.chrome.driver", System.getProperty('webdriver.chrome.driver')
    systemProperty "webdriver.gecko.driver", System.getProperty('webdriver.gecko.driver')
}
gradle.properties
gebVersion=2.3
seleniumVersion=3.12.0
webdriverBinariesVersion=1.4
hibernateCoreVersion=5.1.5.Final
chromeDriverVersion=2.44 (2)
geckodriverVersion=0.23.0 (3)
seleniumSafariDriverVersion=3.14.0
1 Includes Webdriver binaries Gradle plugin.
2 Set the appropriate Webdriver for Chrome version.
3 Set the appropriate Webdriver for Firefox version.
4 Includes the Grails Geb Plugin dependency which has a transitive dependency to geb-spock. This is the dependency necessary to work with Geb and Spock.
5 Selenium and different driver dependencies.

Create also a Geb Configuration file at src/integration-test/resources/GebConfig.groovy.

src/integration-test/resources/GebConfig.groovy
import org.openqa.selenium.chrome.ChromeDriver
import org.openqa.selenium.chrome.ChromeOptions
import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.firefox.FirefoxOptions
import org.openqa.selenium.safari.SafariDriver

environments {

    // You need to configure in Safari -> Develop -> Allowed Remote Automation
    safari {
        driver = { new SafariDriver() }
    }

    // run via “./gradlew -Dgeb.env=chrome iT”
    chrome {
        driver = { new ChromeDriver() }
    }

    // run via “./gradlew -Dgeb.env=chromeHeadless iT”
    chromeHeadless {
        driver = {
            ChromeOptions o = new ChromeOptions()
            o.addArguments('headless')
            new ChromeDriver(o)
        }
    }

    // run via “./gradlew -Dgeb.env=firefoxHeadless iT”
    firefoxHeadless {
        driver = {
            FirefoxOptions o = new FirefoxOptions()
            o.addArguments('-headless')
            new FirefoxDriver(o)
        }
    }

    // run via “./gradlew -Dgeb.env=firefox iT”
    firefox {
        driver = { new FirefoxDriver() }
    }
}

Deprecated classes

The following classes, which were deprecated in Grails 3.x, have been removed in Grails 4. Please, check the list below to find a suitable replacement:

Removed Class

Alternative

org.grails.datastore.gorm.validation.constraints.UniqueConstraint

org.grails.datastore.gorm.validation.constraints.builtin.UniqueConstraint

grails.util.BuildScope

grails.transaction.GrailsTransactionTemplate

grails.gorm.transactions.GrailsTransactionTemplate

org.grails.transaction.transform.RollbackTransform

org.grails.datastore.gorm.transactions.transform.RollbackTransform

grails.transaction.NotTransactional

grails.gorm.transactions.NotTransactional

grails.transaction.Rollback

grails.gorm.transactions.Rollback

grails.transaction.Transactional

grails.gorm.transactions.Transactional

org.grails.config.FlatConfig

org.grails.core.metaclass.MetaClassEnhancer

Use traits instead.

org.grails.core.util.ClassPropertyFetcher

org.grails.datastore.mapping.reflect.ClassPropertyFetcher

org.grails.transaction.transform.TransactionalTransform

org.grails.datastore.gorm.transactions.transform.TransactionalTransform

grails.core.ComponentCapableDomainClass

grails.core.GrailsDomainClassProperty

Use the org.grails.datastore.mapping.model.MappingContext API instead

org.grails.core.DefaultGrailsDomainClassProperty

org.grails.core.MetaGrailsDomainClassProperty

org.grails.core.support.GrailsDomainConfigurationUtil

Use the org.grails.datastore.mapping.model.MappingContext and org.grails.datastore.mapping.model.MappingFactory APIs instead

org.grails.plugins.domain.DomainClassPluginSupport

org.grails.plugins.domain.support.GormApiSupport

org.grails.plugins.domain.support.GrailsDomainClassCleaner

Handled by org.grails.datastore.mapping.model.MappingContext now

grails.validation.AbstractConstraint

Use org.grails.datastore.gorm.validation.constraints.AbstractConstraint instead

grails.validation.AbstractVetoingConstraint

 org.grails.datastore.gorm.validation.constraints.AbstractVetoingConstraint

grails.validation.CascadingValidator

grails.gorm.validation.CascadingValidator

grails.validation.ConstrainedProperty

grails.gorm.validation.ConstrainedProperty

grails.validation.Constraint

grails.gorm.validation.Constraint

grails.validation.ConstraintFactory

org.grails.datastore.gorm.validation.constraints.factory.ConstraintFactory

grails.validation.VetoingConstraint

grails.gorm.validation.VetoingConstraint

grails.validation.ConstraintException

org.grails.validation.BlankConstraint

org.grails.datastore.gorm.validation.constraints.BlankConstraint

org.grails.validation.ConstrainedPropertyBuilder

org.grails.datastore.gorm.validation.constraints.builder.ConstrainedPropertyBuilder

org.grails.validation.ConstraintDelegate

org.grails.validation.ConstraintsEvaluatorFactoryBean

org.grails.datastore.gorm.validation.constraints.eval.ConstraintsEvaluator

org.grails.validation.CreditCardConstraint

org.grails.datastore.gorm.validation.constraints.CreditCardConstraint

org.grails.validation.DefaultConstraintEvaluator

org.grails.datastore.gorm.validation.constraints.eval.DefaultConstraintEvaluator

org.grails.validation.DomainClassPropertyComparator

org.grails.validation.EmailConstraint

org.grails.datastore.gorm.validation.constraints.EmailConstraint

org.grails.validation.GrailsDomainClassValidator

grails.gorm.validation.PersistentEntityValidator

org.grails.validation.InListConstraint

org.grails.datastore.gorm.validation.constraints.InListConstraint

org.grails.validation.MatchesConstraint

org.grails.datastore.gorm.validation.constraints.MatchesConstraint

org.grails.validation.MaxConstraint

org.grails.datastore.gorm.validation.constraints.MaxConstraint

org.grails.validation.MaxSizeConstraint

org.grails.datastore.gorm.validation.constraints.MaxSizeConstraint

org.grails.validation.MinConstraint

org.grails.datastore.gorm.validation.constraints.MinConstraint

org.grails.validation.MinSizeConstraint

org.grails.datastore.gorm.validation.constraints.MinSizeConstraint

org.grails.validation.NotEqualConstraint

org.grails.datastore.gorm.validation.constraints.NotEqualConstraint

org.grails.validation.NullableConstraint

org.grails.datastore.gorm.validation.constraints.NullableConstraint

org.grails.validation.RangeConstraint

org.grails.datastore.gorm.validation.constraints.RangeConstraint

org.grails.validation.ScaleConstraint

org.grails.datastore.gorm.validation.constraints.ScaleConstraint

org.grails.validation.SizeConstraint

org.grails.datastore.gorm.validation.constraints.SizeConstraint

org.grails.validation.UrlConstraint

org.grails.datastore.gorm.validation.constraints.UrlConstraint

org.grails.validation.ValidatorConstraint

org.grails.datastore.gorm.validation.constraints.ValidatorConstraint

org.grails.validation.routines.DomainValidator

Replaced by newer version of commons-validation

org.grails.validation.routines.InetAddressValidator

Replaced by newer version of commons-validation

org.grails.validation.routines.RegexValidator

Replaced by newer version of commons-validation

org.grails.validation.routines.ResultPair

Replaced by newer version of commons-validation

org.grails.validation.routines.UrlValidator

Replaced by newer version of commons-validation

grails.web.JSONBuilder

groovy.json.StreamingJsonBuilder

Grails-Java8

For those who have added a dependency on the grails-java8 plugin, all you should need to do is simply remove the dependency. All of the classes in the plugin have been moved out to their respective projects.

Profiles Deprecation

A few of the profiles supported in Grails 3.x will no longer be maintained going forward and as a result it is no longer possible to create applications when them in the shorthand form. When upgrading existing projects, it will be necessary to supply the version for these profiles.

  • org.grails.profiles:angularjsorg.grails.profiles:angularjs:1.1.2

  • org.grails.profiles:webpackorg.grails.profiles:webpack:1.1.6

  • org.grails.profiles:react-webpackorg.grails.profiles:react-webpack:1.0.8

Scheduled Methods

In Grails 3 no configuration or additional changes were necessary to use the Spring @Scheduled annotation. In Grails 4 you must apply the @EnableScheduling annotation to your application class in order for scheduling to work.