(Quick Reference)

13 Validation and Constraints

Version: 2023.3.0-SNAPSHOT

13 Validation and Constraints

Constraints are how you define validation when using GORM entities.

13.1 Applying Constraints

Within a domain class constraints are defined with the constraints property that is assigned a code block:

class User {
    String login
    String password
    String email
    Integer age

    static constraints = {
      ...
    }
}

You then use method calls that match the property name for which the constraint applies in combination with named parameters to specify constraints:

class User {
    ...

    static constraints = {
        login size: 5..15, blank: false, unique: true
        password size: 5..15, blank: false
        email email: true, blank: false
        age min: 18
    }
}

In this example we’ve declared that the login property must be between 5 and 15 characters long, it cannot be blank and must be unique. We’ve also applied other constraints to the password, email and age properties.

By default, all domain class properties are not nullable (i.e. they have an implicit nullable: false constraint).

Note that constraints are only evaluated once which may be relevant for a constraint that relies on a value like an instance of java.util.Date.

class User {
    ...

    static constraints = {
        // this Date object is created when the constraints are evaluated, not
        // each time an instance of the User class is validated.
        birthDate max: new Date()
    }
}

13.2 Referencing Instances in Constraints

It’s very easy to attempt to reference instance variables from the static constraints block, but this isn’t legal in Groovy (or Java). If you do so, you will get a MissingPropertyException for your trouble. For example, you may try the following:

class Response {
    Survey survey
    Answer answer

    static constraints = {
        survey blank: false
        answer blank: false, inList: survey.answers
    }
}

See how the inList constraint references the instance property survey? That won’t work. Instead, use a custom validator constraint:

class Response {
    ...
    static constraints = {
        survey blank: false
        answer blank: false, validator: { val, Response obj -> val in obj.survey.answers }
    }
}

In this example, the obj argument to the custom validator is the domain instance that is being validated, so we can access its survey property and return a boolean to indicate whether the new value for the answer property, val, is valid.

13.3 Cascade constraints validation

If GORM entity references some other entities, then during its constraints evaluation (validation) the constraints of the referenced entity could be evaluated also, if needed. There is a special parameter cascadeValidate in the entity mappings section, which manage the way of this cascaded validation happens.

class Author {
    Publisher publisher

    static mapping = {
        publisher(cascadeValidate: "dirty")
    }
}

class Publisher {
    String name

    static constraints = {
        name blank: false
    }
}

The following table presents all options, which can be used:

Option Description

none

Will not do any cascade validation at all for the association.

default

The DEFAULT option. GORM performs cascade validation in some cases.

dirty

Only cascade validation if the referenced object is dirty via the DirtyCheckable trait. If the object doesn’t implement DirtyCheckable, this will fall back to default.

owned

Only cascade validation if the entity owns the referenced object.

It is possible to set the global option for the cascadeValidate:

app/conf/application.groovy
grails.gorm.default.mapping = {
    '*'(cascadeValidate: 'dirty')
}

13.4 Constraints Reference

The following table summarizes the available constraints with a brief example:

Constraint Description Example

blank

Validates that a String value is not blank

login(blank:false)

creditCard

Validates that a String value is a valid credit card number

cardNumber(creditCard: true)

email

Validates that a String value is a valid email address.

homeEmail(email: true)

inList

Validates that a value is within a range or collection of constrained values.

name(inList: ["Joe"])

matches

Validates that a String value matches a given regular expression.

login(matches: "[a-zA-Z]+")

max

Validates that a value does not exceed the given maximum value.

age(max: new Date()) price(max: 999F)

maxSize

Validates that a value’s size does not exceed the given maximum value.

children(maxSize: 25)

min

Validates that a value does not fall below the given minimum value.

age(min: new Date()) price(min: 0F)

minSize

Validates that a value’s size does not fall below the given minimum value.

children(minSize: 25)

notEqual

Validates that that a property is not equal to the specified value

login(notEqual: "Bob")

nullable

Allows a property to be set to null - defaults to false.

age(nullable: true)

range

Uses a Groovy range to ensure that a property’s value occurs within a specified range

age(range: 18..65)

scale

Set to the desired scale for floating point numbers (i.e. the number of digits to the right of the decimal point).

salary(scale: 2)

size

Uses a Groovy range to restrict the size of a collection or number or the length of a String.

children(size: 5..15)

unique

Constrains a property as unique at the database level

login(unique: true)

url

Validates that a String value is a valid URL.

homePage(url: true)

validator

Adds custom validation to a field.

See documentation

13.5 Constraints and Database Mapping

Although constraints are primarily for validation, it is important to understand that constraints can affect the way in which the database schema is generated.

Where feasible, GORM uses a domain class’s constraints to influence the database columns generated for the corresponding domain class properties.

Consider the following example. Suppose we have a domain model with the following properties:

String name
String description

By default, in MySQL, GORM would define these columns as

Column Data Type

name

varchar(255)

description

varchar(255)

But perhaps the business rules for this domain class state that a description can be up to 1000 characters in length. If that were the case, we would likely define the column as follows if we were creating the table with an SQL script.

Column Data Type

description

TEXT

Chances are we would also want to have some application-based validation to make sure we don’t exceed that 1000 character limit before we persist any records. In GORM, we achieve this validation with constraints. We would add the following constraint declaration to the domain class.

static constraints = {
    description maxSize: 1000
}

This constraint would provide both the application-based validation we want and it would also cause the schema to be generated as shown above. Below is a description of the other constraints that influence schema generation.

Constraints Affecting String Properties

  • inList

  • maxSize

  • size

If either the maxSize or the size constraint is defined, Grails sets the maximum column length based on the constraint value.

In general, it’s not advisable to use both constraints on the same domain class property. However, if both the maxSize constraint and the size constraint are defined, then GORM sets the column length to the minimum of the maxSize constraint and the upper bound of the size constraint. (GORM uses the minimum of the two, because any length that exceeds that minimum will result in a validation error.)

If the inList constraint is defined (and the maxSize and the size constraints are not defined), then GORM sets the maximum column length based on the length of the longest string in the list of valid values. For example, given a list including values "Java", "Groovy", and "C++", GORM would set the column length to 6 (i.e., the number of characters in the string "Groovy").

Constraints Affecting Numeric Properties

  • min

  • max

  • range

If the max, min, or range constraint is defined, GORM attempts to set the column precision based on the constraint value. (The success of this attempted influence is largely dependent on how Hibernate interacts with the underlying DBMS.)

In general, it’s not advisable to combine the pair min/max and range constraints together on the same domain class property. However, if both of these constraints is defined, then GORM uses the minimum precision value from the constraints. (GORM uses the minimum of the two, because any length that exceeds that minimum precision will result in a validation error.)

  • scale

If the scale constraint is defined, then GORM attempts to set the column scale based on the constraint value. This rule only applies to floating point numbers (i.e., java.lang.Float, java.Lang.Double, java.lang.BigDecimal, or subclasses of java.lang.BigDecimal). The success of this attempted influence is largely dependent on how Hibernate interacts with the underlying DBMS.

The constraints define the minimum/maximum numeric values, and GORM derives the maximum number of digits for use in the precision. Keep in mind that specifying only one of min/max constraints will not affect schema generation (since there could be large negative value of property with max:100, for example), unless the specified constraint value requires more digits than default Hibernate column precision is (19 at the moment). For example:

someFloatValue max: 1000000, scale: 3

would yield:

someFloatValue DECIMAL(19, 3) // precision is default

but

someFloatValue max: 12345678901234567890, scale: 5

would yield:

someFloatValue DECIMAL(25, 5) // precision = digits in max + scale

and

someFloatValue max: 100, min: -100000

would yield:

someFloatValue DECIMAL(8, 2) // precision = digits in min + default scale