Apache Grails 8.0.0 - Release Announcement

By James Daugherty

October 8, 2026

The Apache Grails community is excited to announce the 8.0.0 release of the Apache Grails Framework!

Grails 8 is the first major release planned and delivered entirely as an Apache Top-Level Project. Since the 8.0.x branch opened in November 2025, more than 4,000 commits across 400 pull requests have landed through six milestones and two release candidates. Grails 8 moves the platform to Java 21, Apache Groovy 5, Spring Boot 4.1 and Spring Framework 7, adds a GORM implementation for Hibernate 7, publishes GORM for Neo4j again, and changes plugin registration, CLI packaging, and GSP compilation.

The Grails documentation is in the best shape it has ever been in. At the Grails 8 launch, 22 Grails guides have been updated to Grails 8.

This post walks through what changed, grouped by area. If you are upgrading, read Behavior Changes to Review Before Upgrading later in this post before you change grailsVersion. The What's New in Grails 8 section of the guide covers the headline features, and the Grails 8 upgrade guide documents every behavior change in detail.

Thousands of volunteer hours went into this release. Thank you to everyone who contributed code, reviews, documentation, issue reports, and testing.

Why use Grails?

  • Rapid application development with high developer productivity
  • Full-stack web framework with everything included
  • DRY & Convention-Over-Configuration: Less boilerplate, sensible defaults
  • Gentle learning curve with Apache Groovy for productivity
  • "Framework of frameworks" built on Apache Groovy, Spring Boot, Spring Framework, Jakarta EE, and Hibernate for enterprise foundations

Download Source Code and Binary Distributions

Apache Grails Downloads

Grails 8 at a Glance

  • Modern platform: Java 21, Gradle 9.8, Apache Groovy 5.1, Spock 2.4, Spring Boot 4.1, Spring Framework 7.0, Spring Security 7.1, Jackson 3, Tomcat 11, Jakarta Servlet 6.1
  • GORM for Hibernate 7, alongside Hibernate 5 (still the default), with database migration support and a dedicated BOM
  • GORM for Neo4j is back, rebuilt for Spring Boot 4 and published again
  • Plugin beans register before Spring Boot auto-configuration, a new beanRegistrar() hook, and a compile-time @GrailsBeans DSL that produces @AutoConfiguration classes
  • Static compilation options for tag libraries and GSP pages, plus a build setting to opt controllers, services and tag libraries into @GrailsCompileStatic
  • Ahead-of-time support: Spring AOT processing, GraalVM native image metadata, and JDK AOT cache training
  • OpenAPI from URL mappings, controllers and constraints, with optional Swagger UI
  • Command-line tooling moves to companion -cli artifacts, keeping CLI dependencies out of your packaged application
  • Embedded MongoDB, MongoDB multi-document transactions, and Spring Data MongoDB interoperability
  • Security hardening, including opt-in deny-by-default data binding, compile-time GORM query safety checks, XML DOCTYPE rejection, default security response headers, canonicalized interceptor matching, and a published threat model
  • Undertow support returns, alongside Tomcat and Jetty
  • Observability: Micrometer spans for controllers, interceptors, data binding, and GSP rendering, with correct uri tags for Grails-mapped requests

What's New in Grails 8

Platform Baseline and Foundations

Grails 8 requires Java 21 and Gradle 9.7 or later. Java 21 is the minimum so the runtime stays on a JVM that is supported through the Grails 8 maintenance window on the support schedule, currently July 2027. Dates on that page can change. A Grails 7 wrapper will not work: the Grails Gradle plugin uses groovyOptions.configurationScriptFile, which Gradle added in 9.7. New applications, and this release, ship the Gradle 9.8.0 wrapper. The standard BOM ships Apache Groovy 5.1.3 and Spock 2.4-groovy-5.0. Groovy compilation keeps invokedynamic off, as it has since Grails 7. Turning it on by default is left for a later major release. Under @GrailsCompileStatic, assigning a static field from a static closure such as constraints or mapping now needs the class name. Indexing a ConfigObject for a missing key creates an empty entry instead of reading back as missing.

The framework is built on Spring Boot 4.1.1 and Spring Framework 7.0. That crosses the Spring Boot 4 boundary and brings the modular auto-configuration layout, Spring Framework 7 API removals, Jackson 3.1.6 as the default JSON library (Jackson 2.22.2 remains BOM-managed for libraries that still require it; a plugin that only brings Jackson 2 Smile, CBOR or YAML can still fail at startup with NoClassDefFoundError unless you add an explicit BOM-managed Jackson 2 databind runtime dependency), Tomcat 11, Jakarta Servlet 6.1, and Spring Boot 4.1 dependency management for Spring Security 7.1, Spring Data 2026.0 and Micrometer 1.17. The BOM also pins Logback at 1.6.3 and the MongoDB driver at 5.12.0, ahead of the versions Spring Boot 4.1.1 manages, for published CVEs.

The Spring Dependency Management plugin is gone. The Grails Gradle plugin adds grails-bom as a native Gradle platform() on application classpaths. Dedicated tool and annotation-processor configurations are left alone. A bundled org.apache.grails.gradle.bom-property-overrides plugin keeps the gradle.properties / ext['slf4j.version'] override workflow. Some keys were renamed, including the Jackson override properties, so a key left under the old name is ignored. grails { bom = null } opts out entirely, and enforcedPlatform("org.apache.grails:grails-bom:$grailsVersion") restores "BOM always wins" semantics. Without an enforced platform, Gradle's highest-version-wins resolution applies. grails-bom now inherits the versions Spring Boot already manages instead of re-pinning them, and the BOM family grows two members: grails-hibernate7-bom and grails-neo4j-bom.

Grails Micronaut is no longer part of grails-core. It is developed and released from apache/grails-micronaut because its Apache Groovy line, Java version and dependencies are not the same as the framework's. The Maven coordinates are unchanged (org.apache.grails:grails-micronaut and the Micronaut BOMs). The integration requires JDK 25 or later and an enforced Micronaut BOM, and it builds against published Grails Core artifacts. It ships together with Grails 8.0.0; after that it can move on its own cadence. A Grails application that does not use Micronaut still runs on Java 21.

Finally, Grails 8 removes a large body of long-deprecated API: grails.web.JSONBuilder, the JSON/XML EnumMarshaller classes, the plugin filter family, the legacy events API and grails-events-compat, Mixin, NamedCriteriaProxy, AetherGrapeEngine, and more. The upgrade guide includes API compatibility notes derived from a japicmp pass over 47 modules, plus a section on known Grails 7 plugin incompatibilities and how to work around them.

GSP, Tag Libraries and Static Compilation

Compiled tag resolution. Tag libraries are now described when they are compiled, and that description resolves tag calls in pages, tag libraries and controllers compiled afterwards. A call whose namespace and tag are known compiles into a direct invocation instead of a metaclass dispatch, and no tag methods are installed onto tag library, page or dispatcher metaclasses any more. The tag is still selected by name at runtime, so overrides and registration order behave exactly as before. In the framework's own measurements this removed about two-thirds of the per-call cost in a statically compiled page and a third in a tag library. Applications whose tag libraries are all described can ask for build errors instead of runtime failures:

grails {
    compileStatic {
        strictTags = true
        dynamicTagNamespaces = ['legacy']   // registered while the application runs
    }
}

Defining a tag as a Closure field still works but is deprecated and now warns at compile time. Method-based tags gained more predictable named-attribute binding, exclude inherited framework and Object methods from dispatch, and preserve real namespace property getters; the Gradle extension defaults preserveParameterNames to true so parameter names are available to typed tag methods.

Application-wide GSP static compilation. Grails 7.2 could compile a page statically with a page directive. Grails 8 makes that something an application can switch on everywhere:

grails {
    compileStatic {
        gsp = true
        strictGsp = true   // optional: report any undeclared name
    }
}

Framework-bound names such as params, flash, request, response, session, servletContext, webRequest, controllerName and actionName are typed and checked without declaring them; a page that declares no model reads its model dynamically instead of failing; names introduced by var/status attributes need no declaration; and <g:set type="int" var="total" .../> gives a variable a type so operators can be applied. Any page can still opt out with <%@ page compileStatic="false" %>. In the framework's own in-process measurements a statically compiled page rendered roughly 1.4 to 1.7 times faster.

Opt every controller, service and tag library into @GrailsCompileStatic from the build, without annotating each class, and controllers annotated with @GrailsCompileStatic can now call tag library methods (link(...), my.customTag(...)) without compile-time errors:

grails {
    compileStatic {
        controllers = true
        services = true
        tagLibs = true   // or: all = true
    }
}

A new application can start out this way: Grails Forge's grails-compile-static feature generates the block above, adding gsp = true when the application uses GSP.

grails -t forge create-app --features=grails-compile-static com.example.demo

GSP in a plain Spring Boot application. org.apache.grails.views:grails-gsp-spring-boot is now published and documented. A Spring Boot application with no grails-app directory, no plugins and Spring MVC routing can render GSP views from src/main/resources/templates, precompile them with compileGroovyPages, and ship without the .gsp sources.

SiteMesh 3 is the default layout engine. New applications use org.apache.grails:grails-sitemesh3 on SiteMesh 3.3.0 for Spring Boot 4. Decoration is applied at the bean-definition level, which fixes layouts silently disappearing when Boot's content-negotiating view resolver wraps the view resolver first, and both SiteMesh 3 tag libraries are method-based. The SiteMesh 2 based grails-layout remains available as an opt-in (--features=grails-layout in Forge).

Reproducible precompiled GSPs. Generated page classes record a source checksum instead of a file modification time, so identical sources compile to identical bytes on every checkout and Gradle's build cache actually hits. Reloading is also more accurate: an edit is caught however close two saves fall, and touching a file no longer recompiles it. compileGroovyPages now forks the JVM the project's Java toolchain asks for, rather than whatever JVM ran Gradle.

Smaller GSP improvements: \${...} renders a literal ${...} (handy for JavaScript template literals), links, forms, pagination, redirects and includes resolve the target controller's namespace automatically when it is unambiguous, and scaffolded views are now expanded and compiled at build time so packaged applications and native images do not generate them on the first request.

Controllers, Requests and Content Negotiation

Every controller as a REST resource. "/$controller"(resources: '*') applies a resources mapping to every controller at once, resolving the controller from the request rather than from the mapping; includes, excludes and group work as they do for a named resource.

Modern content negotiation. The framework now supplies built-in MIME defaults (all, atom, css, csv, form, html, js, json, multipartForm, pdf, rss, text, hal, xml), so new applications no longer carry a grails.mime.types block, and grails.mime.mergeDefaults: true layers a declared block over them. The Accept header is honored for every client: the Firefox 2-era user-agent exclusion list defaults to unset, so a browser fetch() asking for application/json gets JSON.

@EnableWebMvc is no longer added to your Application class. Spring Boot's WebMvcAutoConfiguration is now active, so spring.mvc.* and spring.web.* properties work, PUT/PATCH/DELETE form bodies are parsed into params, and locale resolution is configurable through grails.i18n.localeResolver (session, cookie, acceptHeader or fixed). Grails removes Boot's catch-all view resolver, static-resource handler and welcome-page mapping automatically so UrlMappings still owns /.

Typed reads of request, session, flash and servlet-context attributes. All four gain the same null-safe converters params has had for years: request.int('page', 1), session.string('timeZone', 'UTC'), flash.string('notice'), servletContext.int('maxUploads', 10). session, request and servletContext also get a typed, non-coercing getAttribute(name, Class) that needs no cast under @GrailsCompileStatic.

Observability. Grails dispatches through URL mappings rather than MVC handler methods, so Micrometer and OpenTelemetry used to record every request as uri=UNKNOWN. Grails 8 tags http.server.requests with /<controller>/<action> and emits inline observations for grails.controller, grails.interceptor, grails.databinding, grails.convert and grails.render, plus gsp.view, gsp.template, gsp.layout and gsp.compile spans. Everything is a no-op when observations are disabled.

A leaner request path. Per-context collaborators are cached, request parameters are built once per request rather than once per candidate mapping, the LocaleContext is restored rather than cleared, and Spring's API-versioning Deprecation/Sunset/Link headers are now emitted for Grails-mapped requests. URL mapping tokens such as $action are now filled from the URI only, never from a request parameter.

The hidden HTTP method filter is off by default. The filter that rewrote POST into PUT/PATCH/DELETE from _method (or the X-HTTP-Method-Override header) ran ahead of Spring Security and forced multipart bodies to be parsed before routing. _method is now resolved inside the dispatcher for PUT, PATCH and DELETE only, the header is no longer honored, and a resources mapping additionally accepts POST /books/$id as update. Set grails.web.hiddenmethod.filter.enabled: true to restore the previous behavior.

File uploads use Spring Boot's multipart configuration. grails.controllers.upload.* is removed in favor of spring.servlet.multipart.* (Boot's defaults are 1 MB per file and 10 MB per request). request is always the outermost servlet request rather than a MultipartHttpServletRequest (request.getFile(...) and friends still work). An oversized upload is a 413 you can map ("413"(controller: 'errors', action: 'tooLarge')) instead of a container error page. Undertow can return an empty 413 without invoking that mapping.

File responses download by default. render(file: ...) sets Content-Disposition: attachment and escapes the filename. Pass inline: true, or set the header yourself, to display the file in the browser.

OpenAPI. grails-openapi builds an OpenAPI document from URL mappings, controllers and constraints, and can export it at build time. Swagger UI is optional through springdoc 3.1. Grails Forge has a feature for it. The module does not require springdoc unless you want the UI.

JSON dates and times follow Spring Boot. render ... as JSON, respond and JSON views write date and time values the way Spring Boot's Jackson JsonMapper does. java.sql.Time is a time of day such as "01:48:46" rather than a UTC instant, LocalTime, Period, Duration and zone types render as values rather than objects of their properties, and a Month binds from its number (9 is SEPTEMBER, where Grails 7 bound that number to OCTOBER). An ISO-8601 date with an offset binds to the instant it names, including values a client sends back from JSON. To keep a Grails 7 format, register a JSON object marshaller for converter-based rendering (render ... as JSON and respond). JSON views need a groovy.json.JsonGenerator.Converter instead; JSON.registerObjectMarshaller does not apply to them. The upgrade guide covers both. 8.0.0-RC1 dropped the milliseconds of some Date values; 8.0.0 restores the Grails 7 form for those.

Data Binding and Security Hardening

Opt-in deny-by-default data binding. Binding stays permissive by default. Setting grails.databinding.denyByDefault: true switches to an allowlist: only properties marked bindable: true, an explicit include: list, or a @BindAllowed(['firstName', 'lastName']) command-object parameter are bound, enforced through nested associations, collections and maps. In either mode an explicit bindable: false is never mass-assigned any more. bindData(book, params, [include: ['title', 'description'], clearMissing: true]) clears included properties that are absent from the binding source.

Compile-time GORM query safety check. A String flattened from an interpolated GString and passed to find, findAll, executeQuery, executeUpdate, findAllWithSql or Neo4j cypherStatic is the one case GORM's runtime parameter binding cannot see, and it now fails the build. Passing the GString directly remains safe and is never flagged. @SuppressWarnings("GormUnsafeQueryString") suppresses a single site; the protectSqlInjectionAttacks=false system property disables the check build-wide.

XML-safe HTML escaping by default. When grails.views.gsp.htmlcodec is not set, the HTML codec used by GSP ${} expressions and encodeAsHTML() now applies the XML-safe escaping that generated applications have selected with htmlcodec: xml since Grails 2.3, instead of HTML4-style escaping. Applications that already set htmlcodec: xml see no difference, and htmlcodec: html4 restores the old output.

XML request bodies. The SAX hardening features Grails sets had been registered under unrecognized https:// identifiers since an HTTPS link sweep in 2024, silently leaving parser defaults in place. Grails 8 uses the registered identifiers, disables XInclude, external entities and DTD loading, and refuses any XML request body that declares a DOCTYPE.

Interceptor and security matcher paths are canonicalized like dispatch. match(uri:), excludes(uri:), the Spring Security compatibility request matcher and the IP address filter previously matched the raw request URI, so /%61dmin/users or /admin;x=1/users reached AdminController without triggering an interceptor for /admin/**. All three now match what the dispatcher has always matched.

Validated query arguments. sort, order and (on Hibernate 7) fetch arguments are validated before a query is built rather than interpolated into HQL, on list(), dynamic finders, criteria, where queries and listOrderBy*. Invalid values throw IllegalArgumentException without echoing the value; order: 'descending' no longer silently sorts ascending.

Default security response headers. Responses include X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, a referrer policy and X-XSS-Protection: 0. Content-Security-Policy and HSTS stay off unless you enable them. Grails preserves headers written by Spring Security or application code and fills missing headers at response commit. Configure grails.security.headers.* to disable or customize the Grails defaults. Disabling a header in Spring Security alone does not remove the Grails default.

Also hardened: withForm tokens are consumed atomically and bounded per session; Grails Wrapper and Forge repository overrides must be HTTPS at every hop; FuseSource Jansi (CVE-2026-8484) is gone from the classpath entirely; and the repository now publishes a THREAT_MODEL.md following the ASF security threat-model rubric.

Internationalization and the Generated UI

Message bundles are resolved by Spring Boot. Grails' custom message source, and the classpath*:*.properties scan it performed at startup, are gone. Base names are recorded at build time per application and per plugin, so the whole spring.messages.* surface (cache-duration, use-code-as-default-message, ...) works as it does in any Boot application, and GraalVM resource hints for bundles are registered automatically. Plugin bundles must now be namespaced on the plugin name (spring-security-core.properties rather than messages.properties).

Available-locale discovery and a real language selector. Grails now knows which locales an application is actually translated into, and <g:localeSelect available="true" pinDefault="true" type="dropdown"/> renders a complete Bootstrap navbar language menu from them (or type="links", or a body form exposing loc.autonym, loc.menuName and friends) instead of listing ~150 JVM locales.

A new welcome page and layout. The generated application's welcome page and layout are internationalized in 18 bundled locales, gain a light/dark/auto theme selector, and replace the static controller list with switchable artefact cards (controllers, domains, services, tag libraries) and a runtime-internals card showing the effective servlet filter pipeline, Spring Security filter chains, MIME types and actuators. The jQuery webjar moves to 4.0.0 and Bootstrap to 5.3.8.

Async, Virtual Threads and Performance

Promises run on Spring Boot's task executor. The default PromiseFactory is now CompletableFuturePromiseFactory, and in an application promises and the default event bus execute on Boot's applicationTaskExecutor, so spring.task.execution.*, graceful shutdown, TaskDecorator beans and spring.threads.virtual.enabled=true all apply without code changes. Grails propagates the GrailsWebRequest into task { } blocks, every promise is a JDK CompletionStage, and async controller responses honor spring.mvc.async.request-timeout (rendering a 503 you can map). A grails.async.promiseFactory=virtual-thread opt-in provides a dedicated virtual-thread factory.

Virtual-thread friendliness on the hot path. GrailsHttpSession replaces synchronized with a ReentrantLock to avoid carrier pinning, the codec extension helpers are statically compiled, codec registration no longer re-adds identical encodeAs* methods to the String metaclass on every reload, and thread-locals are cleared instead of retained across pooled threads.

GORM startup now scales as O(entities + connections). GormEnhancer used to eagerly build static, instance and validation APIs for every entity x connection/tenant pair; a new GormRegistry builds them lazily on first use, which matters most for applications with many domain classes or schema/database multi-tenancy.

A JMH benchmark suite now runs on every pull request labeled performance, comparing URL mappings, data binding, GSP rendering, interceptors and views against the base branch.

Plugins and Spring Integration

Plugin beans register before Spring Boot auto-configuration. A bean a plugin contributes now takes precedence over a Spring Boot default guarded by @ConditionalOnMissingBean, so Boot backs off with no need to override or remove it afterwards. Plugins register beans through the new beanRegistrar() hook, which returns a Spring Framework BeanRegistrar; the doWithSpring bean builder DSL is deprecated but continues to work, and a statically compilable doWithSpring(BeanBuilder) method form is available as a stepping stone.

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

@GrailsBeans: a bean-wiring DSL compiled into real auto-configuration. A beans = { ... } block on the Application class or on a *GrailsPlugin descriptor is rewritten at compile time into @Bean factory methods on an @AutoConfiguration class. Nothing DSL-shaped survives in bytecode, the methods are statically compiled, and Boot's own conditional pipeline applies:

class Application extends GrailsAutoConfiguration {
    def beans = {
        bean(MyService)

        bean('mailSender', JavaMailSenderImpl).conditionalOnMissingBean(JavaMailSender) {
            new JavaMailSenderImpl(host: 'localhost')
        }
    }
}

Qualifiers such as .conditionalOnMissingBean(...), .conditionalOnProperty('app.offline', havingValue: 'false'), .conditionalOnClass(...), .conditionalOnGrailsEnv('development'), .primary(), .lazy() and .scope(...) chain onto a declaration, @AutoConfiguration(before=/after=) ordering and nested conditional group(...) blocks are supported, the AutoConfiguration.imports entry is written for you, and eight of the framework's own auto-configurations are now authored this way.

More framework beans are cleanly overridable (localeResolver, messageSource, grailsCorsFilter, exceptionHandler, grailsCacheManager and others are @ConditionalOnMissingBean), spring.main.allow-bean-definition-overriding and allow-circular-references are configurable instead of hardcoded, grails.spring.bean.packages scanning works from any working directory, the Grails plugin lifecycle runs only for a Grails application, and unresolved plugin dependencies are reported with the exact cause instead of a bare failure.

Configuration metadata. Every Grails module jar now ships a standard META-INF/spring-configuration-metadata.json, generated at build time from Groovy @ConfigurationProperties classes, Java classes and Groovy configuration DSL scripts. IDEs get completion, types and defaults for grails.* keys in application.yml, and the guide gains a generated Application Properties reference.

Grails Data (GORM)

GORM for Hibernate 7. Grails 8 does not ship a Hibernate 6 line. It keeps Hibernate 5 as the default and adds a complete GORM implementation for Hibernate ORM 7.4, so applications are not asked to migrate through an intermediate major. The version-agnostic GORM test suite runs natively against both, so every contract is verified on both lines. Switching an application means replacing the Hibernate 5 plugin with the Hibernate 7 one and using the Hibernate 7 BOM in place of grails-bom:

dependencies {
    implementation enforcedPlatform("org.apache.grails:grails-hibernate7-bom:8.0.0")
    implementation "org.apache.grails:grails-data-hibernate7"   // replaces grails-data-hibernate5
}

Apply the same BOM to the buildscript classpath and to buildSrc, if you have one; leaving the default grails-bom there alongside the Hibernate 7 BOM produces dependency conflicts. The upgrade guide lists every Hibernate 5 to 7 change that can affect application code. Hibernate 7 criteria queries also support sqlRestriction, as Hibernate 5 does.

Database migration ships for Hibernate 7 as grails-data-hibernate7-dbmigration (with its dbm-* commands in a companion -cli artifact), and Grails Forge generates Hibernate 7 applications directly with --data=hibernate7. Because Spring Framework 7 removed org.springframework.orm.hibernate5 entirely, Grails vendors that support code for both Hibernate lines. On Hibernate 7, GORM now tracks changes from persist rather than insert (a new entity with a pre-insert identifier starts at version 0 with a single INSERT), persistence listeners receive Persist and Merge events, and id generator: 'sequence' derives its sequence name from the table. Hibernate 5 gains an in-tree ByteBuddy proxy factory that replaces a third-party dependency, and reading a proxy's identifier never initializes it on either line.

Locking. book.refresh(lock: true) reloads an entity's state under a pessimistic write lock, Book.lock(id, refresh: true) reloads an already-managed instance under the lock, and both accept type:/lock: with any jakarta.persistence.LockModeType on Hibernate 5 and 7. entity.mutex { } now reloads under an exclusive lock, so it waits for a competing writer instead of failing with an optimistic locking exception. The reload discards unflushed changes. The instance must already be attached, and a transaction must be active.

The default dataSource bean is always registered. With GORM for Hibernate, an application without a dataSource block now gets a dataSource bean for the default (jdbc:h2:mem:grailsDB) data source the datastore already used, on Hibernate 5 and 7.

Domain properties are nullable by default, aligning GORM with JPA, Spring Data and Bean Validation; declare nullable: false for required properties, or restore the old default with grails.gorm.default.nullable: false. count() returns Long instead of silently narrowing to Integer.

Datastore-native identity types. grails { gorm { defaultIdType = 'native' } } lets a domain class that declares no id take the identity type of the GORM implementation it is mapped with (String for MongoDB, Long for Hibernate), resolved at compile time from mapWith, so the same source works against either datastore.

Also new: collection properties stay dirty-checked after reassignment and iterator-based removals are tracked on interception-based stores; a static Book.deleteAll() removes every instance; calls on a class inside Book.secondary.withTransaction { } route to that connection across Hibernate, MongoDB and Neo4j; auto-timestamp suppression is thread-safe; a new Multi-Tenancy chapter documents Tenants.withId, withTenant and eachTenant; and GORM entities stay on the DevTools restart class loader, ending the Not an entity errors after a restart. Criteria closures declare Closure.DELEGATE_FIRST, which matches the runtime and stops a nested criteria closure under @CompileStatic from adding its restrictions to the outer criteria. A transaction started inside another joins it, on MongoDB and in DataTest as well as on Hibernate and Neo4j.

GORM for MongoDB

  • Multi-document transactions. grails.mongodb.transactional = true makes withTransaction { } and @Transactional drive a server-side transaction over a ClientSession, so every read and write commits or rolls back atomically (replica set or sharded cluster required). A nested REQUIRED transaction joins the outer one instead of committing early. REQUIRES_NEW stays isolated, and the same rollback propagation is fixed on Neo4j.
  • Spring Data MongoDB interoperability. The optional grails-data-mongodb-spring-data module auto-configures a MongoTemplate, MongoDatabaseFactory and transaction manager over GORM's existing MongoClient, so GORM and Spring Data repositories share one connection and one transaction inside a single @Transactional method.
  • Embedded MongoDB. org.apache.grails:grails-data-mongodb-embedded starts a server when the connection URL names the host embedded (mongodb://embedded/bookstore), with an in-memory backend for millisecond startup or a real mongod via Flapdoodle, single-node replica sets for transaction tests, and DevTools restart reuse. Forge wires it in by default for MongoDB applications, so a generated application runs without MongoDB or Docker installed.
  • TTL, text and reconciled indexes. indexAttributes: [expireAfterSeconds: 3600] declares a TTL index, indexAttributes: [type: 'text'] a text index, changed TTLs are applied in place with collMod, and grails.mongodb.buildIndexes: false / buildIndexesAsync: true take index creation off the startup path.
  • Index cleanup. GORM still never drops an index on its own. MongoDatastore.findUndeclaredIndexes() lists indexes on mapped collections that no domain class declares any more, and dropUndeclaredIndexes() drops them as a deliberate step; findMissingIndexes() reports declared indexes a collection lacks. Index mappings combined with a '*' default in grails.gorm.default.constraints or grails.gorm.default.mapping are no longer silently discarded.
  • String ids are stored as ObjectId by default. Code still sees the 24-character hex string; applications with existing BSON-string _id values should pin grails.mongodb.stringIds.defaultStoredAs: string. Association and IN criteria coerce correctly, updateAll is supported, and the pre-GORM-6 mapping engine is deprecated.
  • Read-only transactions no longer flush, matching every other GORM datastore; a MongoClientSettingsBuilderCustomizer bean customizes the driver; an externally supplied MongoClient is no longer closed by GORM, and grails.mongodb.* settings are honored when one is supplied.
  • CRaC checkpoint/restore readiness. An application using GORM for MongoDB can be checkpointed while running or as its context refreshes (-Dspring.context.checkpoint=onRefresh). GORM stops every client before a checkpoint and restarts it on restore, and the MongoClient it hands out stays the same object throughout. In the framework's measurement on Azul Zulu 25 with CRaC, restore took 54 ms compared with 3.4 s to start cold.

GORM for Neo4j

GORM for Neo4j was never built or published for the Grails 7 line. Grails 8 brings it back, migrated to Groovy 5, Jakarta and Spring Boot 4, on the new GormRegistry, and published as org.apache.grails:grails-data-neo4j, org.apache.grails:grails-data-neo4j-spring-boot, org.apache.grails.data:grails-data-neo4j-core and org.apache.grails:grails-neo4j-bom. Grails Forge generates a Neo4j application with --data=neo4j, named connections start and route correctly, and Cypher query strings participate in the compile-time query safety check.

Build, CLI and Developer Tooling

Commands move to companion -cli artifacts. ApplicationCommand implementations (the dbm-* migration commands, the scaffolding generate-* commands, url-mappings-report, schema-export, the Spring Security s2-* commands) no longer ship inside runtime plugin jars. Each command-bearing module publishes a companion artifact with a -cli suffix, applications consume them through a new grailsCli Gradle configuration that never reaches runtimeClasspath, bootJar or bootWar, and the Grails Gradle plugin discovers every companion advertised by your dependency graph automatically. Plugin authors get the whole publishing side from the new org.apache.grails.gradle.grails-plugin-cli plugin, and grails { legacyCommandSupport = true } keeps unchanged Grails 7 command plugins working through a deprecated bridge.

Developer tooling leaves the application classpath. Jansi, grails-console, the Groovy console and shell, and JLine no longer reach runtimeClasspath; ANSI output is controlled by Spring Boot's spring.output.ansi.enabled, and a validateProductionClasspath check keeps it that way.

A faster, project-aware CLI. A release CLI never re-checks remote maven-metadata.xml, dependency resolution runs in parallel (-Dgrails.dependency.resolution.threads=N), grails --help inside a project lists the application's own commands, Grails tasks appear in the Grails Gradle task group, and the start scripts grant native access so JLine produces no JEP 472 warnings on JDK 24+.

No generated logback-spring.xml. New applications rely on Spring Boot's Logback defaults (logging.level.*, logging.pattern.console, logging.file.name); --features=logback-config generates an editable starter file for those who want one. grails.logging.stackTraceFiltererClass and grails.exceptionresolver.logFullStackTraceOnFilter now govern every stack-trace sanitizer, not only the exception resolver.

Gradle plugin changes. The vestigial gspCompile configuration is removed (GSPs compile against compileClasspath), the combined Groovy compiler configuration script is produced by a proper generateCompileGroovyGrailsCompilerConfig task, and the grails { } extension gains bom, cliAutoProvision, legacyCommandSupport, preserveParameterNames, compileStatic { }, gorm { }, i18n { }, aotCache { } and nativeMetadata { } blocks. importJavaTime is removed: Groovy 5 already imports java.time, and a build that still sets the flag fails until the line is deleted.

Asset-pipeline 5.2. A % or * in an asset path stands for one directory, in development and in a packaged application. Grails 7 let a single % stand for several directories inside a jar, so webjars/%/dist/jquery.js found webjars/jquery/3.7.1/dist/jquery.js and now finds nothing. Write one % per directory, or %% for zero or more. When several directories match, the highest version wins. A path that no longer resolves is logged by assetCompile and left out of the build.

DevTools. On macOS, Grails' directory watcher no longer silently falls back to one-second polling whenever JNA is on the classpath (add io.methvin:directory-watcher for native FSEvents); Spring Boot 4 turns LiveReload off by default (Forge enables it in application-development.yml); and the restart class loader is resolved through Tomcat's web-app loader so GORM entities are found after a restart.

Test fixtures. JsonViewTest, GraphQLSchemaSpec, GrailsWebMockUtil and the Mock* resource-loader helpers are no longer on production classpaths; request them with testImplementation testFixtures('org.apache.grails:grails-core') and friends.

Deployment and Runtime

Ahead-of-time processing. Applying Spring Boot's AOT plugin adds a processAot task that generates bean definitions as source at build time, so a start with -Dspring.aot.enabled=true skips classpath scanning and annotation processing. The Grails Gradle plugin sets grails.env to production on processAot automatically, and the guide documents what AOT changes at runtime and what it does not yet cover (beans contributed through a plugin's beanRegistrar() are re-registered at runtime).

GraalVM native image metadata. generateNativeMetadata writes reachability metadata for your artefacts and compiled pages from the build output, and traceNativeMetadata runs the application under GraalVM's tracing agent along the paths and forms you list (grails { nativeMetadata { paths = ['/', '/book']; forms = ['/login?username=admin&password=secret'] } }), carrying the login session so pages behind authentication are traced.

JDK AOT cache training (Project Leyden). On JDK 25 or later, grails.aotCache trains a JDK AOT cache by running the packaged application once over the paths you list and recording the classes it loaded and linked and the methods it ran:

grails {
    aotCache {
        enabled = true
        paths = ['/', '/login', '/book/index']
    }
}

Enabling it adds an extractAotCacheApplication task and a trainAotCache task, which you run explicitly. Training unpacks the executable jar into build/aot-cache/application, runs it over the listed paths, and writes the cache beside it.

Warning: training is a real run of the application, in the production environment by default. It executes bootstrap code, connects to whatever the configuration points at, and writes whatever that code writes. Point it at a build-time or throwaway database, never at production.

./gradlew trainAotCache
java -XX:AOTCache=build/aot-cache/myapp.aot -Dspring.aot.enabled=true -jar build/aot-cache/application/myapp.jar

Training is POSIX-only; running with a trained cache is not. In the framework's measurement of a GORM/Hibernate application with Spring Security and the asset pipeline, startup fell from 2.594 s to 0.933 s, and a generated aot-cache.properties records the JDK build, archive checksum and training arguments so a deployment can tell whether the cache still applies.

Undertow is back. Spring Boot 4 dropped its Undertow starter before Undertow had Jakarta Servlet 6.1 support; Grails 8 restores it as org.apache.grails:grails-undertow (Undertow 2.4.3 with the io.undertow.ee servlet and websocket modules). Tomcat 11, Jetty 12 and Undertow are all supported, Forge's --servlet=undertow works again, and the startup banner names the container in use. server.undertow.max-http-post-size now defaults to 2 MB; set it to -1 to restore the previous unlimited size.

The startup banner is colored, reports the servlet container and Spring Security version by default, and shows how the application was started (NATIVE, AOT CACHE or AOT) when that is worth saying. For WAR deployment to an external container, providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat-runtime' replaces the old Tomcat starter.

Testing

  • Latency testing. grails-testing-support-latency injects random artificial latency into the application under test (grails.testing.latency.enabled, min-delay, max-delay, probability, url-patterns, seed). A fixed seed makes a run reproducible.
  • Unit tests can declare beans the way the application does. beans blocks, beanRegistrar() and nested configuration classes work in unit tests. Testing doWithSpring() is deprecated.
  • httpPostForm(...) on HttpClientSupport sends application/x-www-form-urlencoded bodies with UTF-8 encoding and repeated keys for collection values.
  • Tag library tests clean up automatically. purgeTagLibMetaClass is gone; mocked tag libraries are cleared and rebuilt between feature methods.
  • Continuous test mode is documented. grails> test-app -continuous reruns the selected tests whenever a source file changes, without leaving the interactive CLI.
  • Embedded MongoDB and single-node replica sets make MongoDB and transaction specs run without Docker.
  • Spring Boot 4's test changes apply: @MockBean/@SpyBean become @MockitoBean/@MockitoSpyBean, and @SpringBootTest no longer auto-configures MockMvc or TestRestTemplate without the corresponding @AutoConfigure* annotation. GrailsUnitTest, GrailsWebUnitTest and @Integration users are unaffected.

Grails Forge

Grails Forge is now a Micronaut 4 application built with Gradle 9.8, and it generates Grails 8 applications with:

  • A new -d, --data option (hibernate5, hibernate7, mongodb, neo4j; -g/--gorm and hibernate remain as legacy aliases) that applies the matching BOM consistently across application dependencies, the buildscript classpath and buildSrc.
  • New features: gorm-hibernate7, gorm-neo4j, gorm-async, grails-compile-static, grails-undertow, grails-layout, logback-config, and three security options: grails-spring-security (User/Role/UserRole domain classes, a scaffolded user controller, static rules and a seeded admin), grails-spring-security-ui (adds user/role management, registration and forgot-password flows), and spring-boot-starter-security (plain Spring Security wired through the new beans DSL on the Application class).
  • Embedded MongoDB wired in by default for MongoDB applications, Testcontainers 2.x artifact names, the internationalized welcome page with a theme selector, no generated Logback file, and a /favicon.ico mapping so the browser's icon fetch can no longer bounce through /login.

Spring Security, Quartz and Other Plugins

Spring Security, Quartz, Redis and Mail are maintained in the grails-core repository and released with the framework, rather than as separately versioned plugin lines.

Grails Spring Security 8.0.0 runs on Spring Security 7.1. A new grails-spring-security-compat module reimplements the access-decision types Spring Security 7 removed (ConfigAttribute, AccessDecisionManager, RoleVoter, AbstractSecurityInterceptor, ...), so the plugin's voter and interceptor model, and application code built on it, keeps compiling and running. The UI plugin is rewritten on Bootstrap 5 and jQuery 4: all 44 screens render through the host application's layout (grails.plugin.springsecurity.ui.gsp.parentLayout), inheriting theme, locale selector and branding, with jQuery UI, DataTables, jGrowl and the bundled CSS gone. The core plugin's login and denied pages are plain Bootstrap 5, eight locales were added to its message bundle, and CAS single sign-out is now opt-in (useSingleSignout, default false), verified against an Apereo CAS Testcontainer.

Grails Quartz 8.0.0 no longer lets one broken schedule take an application with it: a trigger that can never fire is logged and left unscheduled (quartz.failOnNeverFiringTriggers: true restores the old behavior), jobs are stamped with the registering application so two applications can share a JDBC job store, the scheduling methods report what is wrong instead of throwing NullPointerException, and a new Long-Running Jobs chapter covers concurrency, misfire handling and interrupting a job.

The Cache, Mail and Redis plugins are wired through beanRegistrar() and the beans DSL, and grails.cache.enabled=yes|on|1 no longer fails startup. The Mail plugin can now override recipients and sender separately: grails.mail.overrideToAddress redirects every to, cc and bcc while keeping the application's sender, and grails.mail.overrideFromAddress sets a fixed sender. grails.mail.overrideAddress still replaces both and now also replaces a from set in the sendMail closure.

Documentation and AI-Assisted Development

The guide gains chapters on Ahead-of-Time Processing, Ahead-of-Time Caching, GSP Static Compilation, GSP in a Spring Boot Application, Compiled Tag Resolution, Multi-Tenancy and Long-Running Quartz Jobs, plus sections on Spring Boot structured logging, Spring HTTP interface clients, continuous testing, the PATCH mapping generated by resources, and a complete Hibernate 7 manual.

Grails 8 also ships agent skills for AI coding assistants. A grails-8-upgrade skill, built from the upgrade guide and refined by upgrading real applications, and a grails-developer skill are published as BOM-managed jars (org.apache.grails.skills:grails-8-upgrade and org.apache.grails.skills:grails-developer), so loading them does not require a Grails checkout. The repository's .agents/skills/ directory holds the skills the maintainers use on the framework itself: groovy-developer, java-developer, gradle-developer, hibernate-developer, test-fixer, violation-fixer and mono-repo-integration.

Behavior Changes to Review Before Upgrading

The upgrade guide covers each of these in depth. The ones most likely to touch an existing application:

Platform and build

  • Java 21 and Gradle 9.7 minimums; the Spring Boot 4 starter renames (spring-boot-starter-web to spring-boot-starter-webmvc) and auto-configuration package relocations
  • The Spring Dependency Management plugin is no longer applied; dependencyManagement { } blocks must be replaced
  • Jackson 3.1.6 is the default JSON library; jackson.version now sets Jackson 2 only under the name jackson-2-bom.version, and jackson-bom.version sets Jackson 3
  • Commands live in -cli artifacts; Jansi is removed; no logback-spring.xml is generated; importJavaTime is gone
  • Asset-pipeline wildcards match one directory; a single % no longer spans webjars/jquery/3.7.1
  • Spring theme resolution is removed with Spring Framework 7; spring-retry is no longer managed by Spring Boot
  • org.apache.grails.data:grails-datamapping-async moved to org.apache.grails:grails-datamapping-async

Web layer and views

  • The hidden HTTP method filter is off by default; grails.controllers.upload.* is replaced by spring.servlet.multipart.*
  • render(file: ...) downloads the file unless you pass inline: true
  • The Accept header is honored for browsers; framework MIME defaults replace the generated grails.mime.types block
  • Enums serialize as their name; JSON dates and times follow Spring Boot, and an ISO-8601 value with an offset binds to the instant it names
  • Without a grails.views.gsp.htmlcodec setting, HTML escaping is XML-safe rather than HTML4-style; htmlcodec: html4 restores the old output
  • Plugin message bundles must be namespaced; spring.messages.* is the configuration surface

Plugins and Spring

  • doWithSpring is deprecated in favor of beanRegistrar(); @Configuration classes declared in resources.groovy are no longer processed
  • grails.mail.overrideAddress now also replaces an explicit from; use grails.mail.overrideToAddress to redirect recipients only

GORM

  • GORM properties are nullable by default; count() returns Long
  • Criteria closures declare Closure.DELEGATE_FIRST; under @CompileStatic, forwarding a plain Closure parameter into where, and, or and friends no longer compiles without a matching @DelegatesTo or a cast
  • The dynamic finder classes in org.grails.datastore.gorm.finders no longer form an inheritance hierarchy; AbstractFinder, AbstractFindByFinder, FindByFinder and FindByBooleanFinder are removed
  • MongoDB String ids are stored as ObjectId by default; read-only transactions no longer flush, and a nested transaction joins the outer one
  • Hibernate 7 applications: id generator: 'sequence' derives its name from the table, and new entities start at version 0

Add runtimeOnly 'org.springframework.boot:spring-boot-properties-migrator' for the duration of the migration to have deprecated and relocated configuration properties reported at startup.

Dependency Upgrades

Grails 8.0.0 ships with the following foundational dependency versions:

ComponentGrails 7.2Grails 8.0
Java (minimum)1721
Gradle8.149.8 (9.7 minimum)
Apache Groovy4.0.x5.1.3
Spock2.4-groovy-4.02.4-groovy-5.0
Spring Boot3.5.x4.1.1
Spring Framework6.2.x7.0.x
Spring Security6.5.x7.1.x
Jackson2.x3.1.6 (Jackson 2.22.2 BOM-managed)
Apache Tomcat10.111.0
Jakarta Servlet6.06.1
Hibernate ORM5.65.6.15 (default) or 7.4.10
SiteMesh 33.23.3.0
Asset Pipeline5.05.2.0
Undertow-2.4.3
MongoDB driver5.95.12.0
jQuery webjar3.74.0.0
Bootstrap webjar5.3.35.3.8

See all managed versions in the grails-bom, including the Hibernate 7 and Neo4j BOM variants.

Release Notes

For the changes as they landed in each milestone, release candidate and the final release, see the GitHub release notes:

Generating a new Grails 8.0.0 application with Grails Forge

Try out Grails today by visiting our online application generator Grails Forge. This is the quickest and the recommended way to get started with Grails.

After installing JetBrains' IntelliJ IDEA and the Grails Plugin, the Grails Application Forge will also be available under New Project in IntelliJ IDEA.

From the command line, the Forge CLI generates a Grails 8 application with the GORM implementation of your choice:

grails -t forge create-app --data=hibernate7 com.example.demo

Within your newly generated project you can access the Grails CLIs with the Grails Wrapper.

See the Types of CLI section in the documentation for details on each CLI.

grails-shell-cli

grailsw

grails-forge-cli

grailsw -t forge

Installing Grails CLIs 8.0.0 with SDKMan

Alternatively, you can quickly install Grails 8.0.0 CLIs (grails-shell-cli and grails-forge-cli) using SDKMan.

See the Types of CLI section in the documentation for details on each CLI.

  1. If you don't have SDKMan installed, follow the instructions at SDKMan Installation Guide to set it up.

  2. Once SDKMan is installed, open your terminal and run the following command to install Grails 8.0.0:

    sdk install grails 8.0.0
    
  3. You're all set! To verify the installation, run:

    grails --version
    

The Grails Shell CLI can be accessed as:

grails

or

grails-shell-cli

The Grails Forge CLI can be accessed as:

grails -t forge

or

grails-forge-cli

Upgrading Your Existing Applications to Grails 8.0.0

If you already have a Grails 7 application and want to upgrade to Grails 8.0.0, follow these steps:

  1. Install JDK 21 or later and update your toolchain, CI pipelines and deployment environments.

  2. Update the Gradle wrapper; a Grails 7 wrapper does not work with the Grails 8 Gradle plugins:

    ./gradlew wrapper --gradle-version 9.8.0
    
  3. Update your application's gradle.properties file to specify Grails 8.0.0 as the desired version.

    grailsVersion=8.0.0
    
  4. Add the Spring Boot properties migrator for the duration of the migration so deprecated and relocated configuration properties are reported at startup:

    runtimeOnly 'org.springframework.boot:spring-boot-properties-migrator'
    
  5. Make any necessary adjustments to your application code, configuration, and dependencies to ensure compatibility with the new version. See Upgrade Guide. If you use an AI coding assistant, point it at the grails-8-upgrade skill.

Normally, Grails Core dependencies are automatically updated using the Grails Bill of Materials (BOM). However, if you have specific versions defined in your build configuration, you may need to manually update them to align with Grails 8.0.0. Grails 8 no longer applies the Spring Dependency Management plugin, so a dependencyManagement { } block in your build must be replaced with plain dependency declarations or resolutionStrategy.force. An override in gradle.properties still wins, but it has to use a property name the BOM declares. Rename jackson.version to jackson-2-bom.version for Jackson 2, and set Jackson 3 with jackson-bom.version. A key left under the old name is ignored.

Exploring Alternative Approaches

If manual dependency updates seem daunting, or you want a more streamlined approach, consider the following alternatives:

1. Use Grails Forge Website

Visit Grails Forge and generate a new Grails application with Grails 8.0.0. Compare the versions in the newly generated application with your existing one to identify any discrepancies. This can serve as a reference point for your update.

2. Automated Dependency Update Bots

Configure automated dependency update bots like Renovate or Dependabot with your source control platform (e.g., GitHub). These bots can automatically detect and update outdated dependencies in your project, including Grails dependencies, saving you time and effort in manual updates.

With these steps and alternative approaches, you should be well on your way to enjoying the exciting features and improvements in Grails 8.0.0.

Why should you try out Grails 8.0.0?

  • Leverage cutting-edge foundations: Built on Java 21, Apache Groovy 5.1, Spring Framework 7.0, Spring Boot 4.1, Jakarta Servlet 6.1 and your choice of Hibernate 5 or Hibernate 7.
  • Start faster and run leaner: Spring AOT processing, JDK AOT cache training, native image metadata, a leaner request path, virtual-thread-friendly internals, and a GORM startup that scales with the size of your domain model.
  • Compile more of your application statically: compiled tag resolution, application-wide GSP static compilation, and a single build setting to opt every controller, service and tag library into @GrailsCompileStatic.
  • Ship a smaller, safer application: CLI dependencies out of your jar, opt-in deny-by-default binding, compile-time query safety, hardened XML and URI handling, and a published threat model.
  • Invest in the future: This version drives ongoing innovation, backed by an active Grails community ensuring support, updates, and evolving capabilities tailored to your needs.

Grails Release Schedule

  • Grails 8.0.0 Release: Officially released on October 8, 2026.
  • Grails 8.0.x Patch Releases: Issued when a fix or a dependency update calls for one, including updates on the Spring Boot 4.1 line. There is no fixed monthly cadence.
  • Grails 8 Support: Grails 8.0.x stays on Spring Boot 4.1, which Spring supports through July 2027. The support schedule lists maintenance support for Grails 8 through July 2027.
  • Grails 9: The next release is planned as Grails 9.0, targeted for the end of November 2026. It is a lighter major release that moves to Apache Groovy 6 with invokedynamic turned on, catching Grails up with Groovy. The work planned for an 8.1 release goes into 9.0 instead, so there will be no 8.1.
  • Grails 10: Planned for Q1 2027 on Apache Groovy 6 and Spring Boot 4.2, aiming to follow Spring Boot 4.2 within about a month. As a major release, it can carry the breaking changes needed to move the framework forward.
  • Grails 7 Support: Final releases of Grails 7.0.x (7.0.18) and 7.1.x (7.1.8) are due in October 2026, after which both lines reach end of support. Grails 7.2.x on Spring Boot 3.5 gets a 7.2.5 release at the same time and is maintained until December 2026. The support page lists extended support options.

These plans and future dates are subject to change.

Apache Grails Mailing Lists

Users Mailing List

The users mailing list will be a General purpose list for questions and discussion about Grails.
Web Archive: https://lists.apache.org/list.html?users@grails.apache.org
Subscribe: Send a blank email to users-subscribe@grails.apache.org

Dev Mailing List

The dev mailing list is focused on the framework implementation and its evolution.
Web Archive: https://lists.apache.org/list.html?dev@grails.apache.org
Subscribe: Send a blank email to dev-subscribe@grails.apache.org

When participating in mailing lists, you should never include any personally identifiable information (PII) like your address, phone number or email address, as all messages sent to these lists are publicly accessible and archived, meaning anyone can view your information. Make sure your email client does not add your signature with these items.

Thank you!

A huge thank you to our amazing community for supporting the Grails Framework over the past 20 years! We're excited for the future and grateful for the opportunity to continue innovating and pushing Grails forward together.

Contributors

We would like to extend our heartfelt thanks to all the contributors who made Grails 8.0.0 possible.
Special thanks to:

Recent Contributors by Project:

Combined Commit List

Their dedication and hard work have significantly contributed to the release of Grails 8.0.0.

Join the Grails Slack Community, share your feedback, and contribute to making Grails Framework even better in the future. Happy coding!

You might also like ...