Controllers and Data Binding
Harry
· 13 Sep 2026
· 3 views
The Role of Controllers
Controllers bridge user input, business logic, and persistence. They read request params, delegate work to services, and produce a response. Keep them thin.
Actions and URLs
class UserController {
def index() {
render "User Home"
}
}index is the default action. URL mappings can be customized:
static mappings = {
"/users"(controller: "user", action: "index")
}Accessing Params
def save() {
println params.username
}params combines query parameters, form data, and URL variables into one map-like object.
Automatic Data Binding
Grails binds request params directly onto a domain object:
def save() {
def user = new User(params)
user.save()
}This is extremely powerful, which is exactly why you must control what gets bound. Never trust client input blindly.
Command Objects (Best Practice)
Command objects give you safe, validated data binding:
class UserCommand {
String username
String email
static constraints = {
username blank: false
email email: true
}
}def save(UserCommand cmd) {
if (cmd.hasErrors()) {
flash.error = "Validation failed"
redirect action: "create"
return
}
userService.register(cmd)
redirect action: "index"
}Binding to Existing Objects
def update(Long id) {
def user = User.get(id)
bindData(user, params)
user.save()
}Rendering and Redirects
render "Hello Grails"
render user as JSON
render view: "create", model: [user: user]
redirect action: "index"Restricting HTTP Methods
static allowedMethods = [
save: "POST",
update: "PUT",
delete: "DELETE"
]RESTful Controllers
class UserController extends RestfulController<User> {
static responseFormats = ['json']
}Common Mistakes
- Heavy business logic in controllers.
- Blindly binding every request param to a domain object.
- Ignoring validation errors.
- Not restricting HTTP methods.
Key Points
- Controllers stay thin and delegate to services.
- Command objects validate input safely.
- bindData gives controlled binding for updates.
- Restrict HTTP methods to match the operation.
