Grails GORM Multi‑Tenancy: Isolate Data with Schema‑Per‑Tenant Strategy
Grails GORM’s schema‑per‑tenant strategy lets you isolate tenant data automatically. This blog walks through configuration, a tenant resolver, domain classes, a concrete example, and trade‑offs to help you decide if it fits your SaaS project.
30 Sept 2026, 04:24 UTC

Problem: How to keep tenant data separate without hand‑crafted WHERE clauses
When building SaaS or multi‑tenant applications, developers often wrestle with how to enforce data isolation. Adding a tenant_id column and sprinkling where tenant_id = :currentTenant everywhere is error‑prone and hard to maintain. Grails GORM offers a built‑in solution that automatically scopes every query to the current tenant, freeing developers from manual filtering.
Thesis: The schema‑per‑tenant strategy gives a clean separation of data while keeping a single database connection pool.
Grails supports three tenancy strategies:
- SINGLE_DATABASE – all tenants share the same tables.
- SCHEMA – each tenant has its own schema inside one database.
- DATABASE – each tenant gets a separate database.
For most SaaS projects that expect dozens to a few hundred tenants, the SCHEMA strategy strikes a balance between isolation, performance, and operational overhead.
Configuring the Strategy
grails:
gorm:
multiTenancy:
strategy: SCHEMA
Place the above in application.yml. The value is case‑insensitive but traditionally written in upper case. The strategy must be set before the application starts; changing it at runtime requires a full restart.
Tenant Resolver Bean
The tenant resolver determines the current tenant ID, typically from the request header, subdomain, or session. Grails injects this ID into every GORM query automatically.
@Component
public class HeaderTenantResolver implements TenantResolver {
@Override
public String getCurrentTenant() {
// Example: tenant ID comes from a custom HTTP header
return RequestContextHolder.currentRequestAttributes()
.getAttribute("X-Tenant-ID", RequestAttributes.SCOPE_REQUEST)
.toString();
}
}
Register the bean in resources.groovy or as a component, and Grails will wire it into the GORM runtime.
Domain Classes and Automatic Scoping
Define your domain classes normally; no tenant column is required. GORM will automatically prefix each table name with the tenant’s schema name.
class Order {
String productName
Integer quantity
static constraints = {
productName blank: false
quantity min: 1
}
}
class Customer {
String name
String email
static constraints = {
name blank: false
email email: true
}
}
When the current tenant is tenantA, GORM will query tenantA.order and tenantA.customer. Switching the tenant changes the schema prefix automatically.
Worked Example: Two Tenants in One Database
- Set up the tenant resolver to return a hard‑coded tenant ID for testing, e.g.,
tenantA. - Create the schemas manually or via a migration script:
CREATE SCHEMA tenantA; CREATE SCHEMA tenantB; - Run the application and save an
Orderand aCustomerundertenantA. - Change the resolver to return
tenantBand repeat the save. - Query
Order.list()in both contexts and verify that each tenant only sees its own records.
To confirm isolation, you can inspect the generated SQL with grails dbm-generate-changelog for each tenant. The output will show table names prefixed with the tenant schema.
Trade‑Offs and Limitations
- Schema limits – Some RDBMS impose a maximum number of schemas per database. If you anticipate thousands of tenants, consider
DATABASEstrategy instead. - Connection overhead – Each schema still shares the same connection pool, but switching schemas per query can add context‑switch overhead. Benchmark if you have high query rates.
- Backup complexity – Backing up per‑tenant data requires dumping each schema separately. Tools like
pg_dumpfor PostgreSQL can target individual schemas. - Security risk – A misconfigured tenant resolver can expose data. Write unit tests that assert
Order.list()returns zero results when the tenant ID is switched.
Actionable Closing
To adopt schema‑per‑tenant in a Grails 5+ project:
- Set
grails.gorm.multiTenancy.strategy: SCHEMAinapplication.yml. - Implement a robust
TenantResolverthat extracts the tenant ID from your chosen source. - Automate schema creation via migration scripts or a provisioning service.
- Write integration tests that switch the tenant context and assert data isolation.
- Monitor database performance and plan for schema limits early.
With these steps, you can rely on GORM to keep tenant data separate, reduce boilerplate, and maintain a clean codebase.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.