(Quick Reference)

10 REST

Version: 8.0.0

10 REST

REST is not really a technology in itself, but more an architectural pattern. REST is very simple and just involves using plain XML or JSON as a communication medium, combined with URL patterns that are "representational" of the underlying system, and HTTP methods such as GET, PUT, POST and DELETE.

Each HTTP method maps to an action type. For example GET for retrieving data, POST for creating data, PUT for updating and so on.

Grails includes flexible features that make it easy to create RESTful APIs. Creating a RESTful resource can be as simple as one line of code, as demonstrated in the next section.

10.1 Domain classes as REST resources

The easiest way to create a RESTful API in Grails is to expose a domain class as a REST resource. This can be done by adding the grails.rest.Resource transformation to any domain class:

import grails.rest.*

@Resource(uri='/books')
class Book {

    String title

    static constraints = {
        title blank:false
    }
}

Simply by adding the Resource transformation and specifying a URI, your domain class will automatically be available as a REST resource in either XML or JSON formats. The transformation will automatically register the necessary RESTful URL mapping and create a controller called BookController.

You can try it out by adding some test data to BootStrap.groovy:

def init = {
    new Book(title:"The Stand").save()
    new Book(title:"The Shining").save()
}

And then hitting the URL http://localhost:8080/books/1, which will render the response like:

<?xml version="1.0" encoding="UTF-8"?>
<book id="1">
    <title>The Stand</title>
</book>

If you change the URL to http://localhost:8080/books/1.json you will get a JSON response such as:

{"id":1,"title":"The Stand"}

If you wish to change the default to return JSON instead of XML, you can do this by setting the formats attribute of the Resource transformation:

import grails.rest.*

@Resource(uri='/books', formats=['json', 'xml'])
class Book {
    ...
}

With the above example JSON will be prioritized. The list that is passed should contain the names of the formats that the resource should expose. The names of formats are defined in the grails.mime.types setting of application.groovy:

grails.mime.types = [
    ...
    json:          ['application/json', 'text/json'],
    ...
    xml:           ['text/xml', 'application/xml']
]

Or the equivalent in application.yml:

grails:
    mime:
        types:
            json:
              - 'application/json'
              - 'text/json'
            xml:
              - 'text/xml'
              - 'application/xml'

See the section on Configuring Mime Types in the user guide for more information.

Instead of using the file extension in the URI, you can also obtain a JSON response using the ACCEPT header. Here’s an example using the Unix curl tool:

$ curl -i -H "Accept: application/json" localhost:8080/books/1
{"id":1,"title":"The Stand"}

This works thanks to Grails' Content Negotiation features.

You can create a new resource by issuing a POST request:

$ curl -i -X POST -H "Content-Type: application/json" -d '{"title":"Along Came A Spider"}' localhost:8080/books
HTTP/1.1 201 Created
Server: Apache-Coyote/1.1
...

Updating can be done with a PUT request:

$ curl -i -X PUT -H "Content-Type: application/json" -d '{"title":"Along Came A Spider"}' localhost:8080/books/1
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
...

Finally a resource can be deleted with DELETE request:

$ curl -i -X DELETE localhost:8080/books/1
HTTP/1.1 204 No Content
Server: Apache-Coyote/1.1
...

As you can see, the Resource transformation enables all of the HTTP method verbs on the resource. You can enable only read-only capabilities by setting the readOnly attribute to true:

import grails.rest.*

@Resource(uri='/books', readOnly=true)
class Book {
    ...
}

In this case POST, PUT and DELETE requests will be forbidden.

10.2 Mapping to REST resources

If you prefer to keep the declaration of the URL mapping in your UrlMappings.groovy file then simply removing the uri attribute of the Resource transformation and adding the following line to UrlMappings.groovy will suffice:

"/books"(resources:"book")

Extending your API to include more end points then becomes trivial:

"/books"(resources:"book") {
    "/publisher"(controller:"publisher", method:"GET")
}

The above example will expose the URI /books/1/publisher.

A more detailed explanation on creating RESTful URL mappings can be found in the URL Mappings section of the user guide.

10.3 Linking to REST resources from GSP pages

The link tag offers an easy way to link to any domain class resource:

<g:link resource="${book}">My Link</g:link>

However, currently you cannot use g:link to link to the DELETE action and most browsers do not support sending the DELETE method directly.

The best way to accomplish this is to use a form submit:

<form action="/book/2" method="post">
         <input type="hidden" name="_method" value="DELETE"/>
</form>

Grails supports overriding the request method via the hidden _method parameter. This is for browser compatibility purposes. This is useful when using restful resource mappings to create powerful web interfaces. To make a link fire this type of event, perhaps capture all click events for links with a data-method attribute and issue a form submit via JavaScript.

The same override can be requested with an X-HTTP-Method-Override header only while the hidden HTTP method filter is enabled; it is disabled by default as of Grails 8.

By default the _method parameter is resolved inside the dispatcher, after multipart handling and after the servlet filter chain. <g:form> emits the parameter as it always has. A resources mapping additionally routes a POST to the member URL at the update action, so a client that cannot send the parameter — an AngularJS $resource and the clients modelled on it POST to save an existing object — reaches update without it.

The previous behaviour — a servlet filter rewriting the request method ahead of the dispatcher — can be restored:

grails-app/conf/application.yml
grails:
    web:
        hiddenmethod:
            filter:
                enabled: true
With the filter disabled, servlet filters and the Spring Security filter chain see the request’s real POST method, and the URL does not distinguish update from delete. Authorization rules keyed on the HTTP method must be reviewed.

10.4 Versioning REST resources

A common requirement with a REST API is to expose different versions at the same time. There are a few ways this can be achieved in Grails.

Versioning using the URI

A common approach is to use the URI to version APIs (although this approach is discouraged in favour of Hypermedia). For example, you can define the following URL mappings:

"/books/v1"(resources:"book", namespace:'v1')
"/books/v2"(resources:"book", namespace:'v2')

That will match the following controllers:

package myapp.v1

class BookController {
    static namespace = 'v1'
}

package myapp.v2

class BookController {
    static namespace = 'v2'
}

This approach has the disadvantage of requiring two different URI namespaces for your API.

Versioning with the Accept-Version header

As an alternative Grails supports the passing of an Accept-Version header from clients. For example you can define the following URL mappings:

"/books"(version:'1.0', resources:"book", namespace:'v1')
"/books"(version:'2.0', resources:"book", namespace:'v2')

Then in the client simply pass which version you need using the Accept-Version header:

$ curl -i -H "Accept-Version: 1.0" -X GET http://localhost:8080/books

Versioning using Hypermedia / Mime Types

Another approach to versioning is to use Mime Type definitions to declare the version of your custom media types (see the section on "Hypermedia as the Engine of Application State" for more information about Hypermedia concepts). For example, in application.groovy you can declare a custom Mime Type for your resource that includes a version parameter (the 'v' parameter):

grails.mime.types = [
    all: '*/*',
    book: "application/vnd.books.org.book+json;v=1.0",
    bookv2: "application/vnd.books.org.book+json;v=2.0",
    ...
}

Or the equivalent in application.yml:

grails:
    mime:
        types:
            all: '*/*'
            book: "application/vnd.books.org.book+json;v=1.0"
            bookv2: "application/vnd.books.org.book+json;v=2.0"
            ...
It is critical that place your new mime types after the 'all' Mime Type because if the Content Type of the request cannot be established then the first entry in the map is used for the response. If you have your new Mime Type at the top then Grails will always try and send back your new Mime Type if the requested Mime Type cannot be established.

Then override the renderer (see the section on "Customizing Response Rendering" for more information on custom renderers) to send back the custom Mime Type in grails-app/conf/spring/resourses.groovy:

import grails.rest.render.json.*
import grails.web.mime.*

beans = {
    bookRendererV1(JsonRenderer, myapp.v1.Book, new MimeType("application/vnd.books.org.book+json", [v:"1.0"]))
    bookRendererV2(JsonRenderer, myapp.v2.Book, new MimeType("application/vnd.books.org.book+json", [v:"2.0"]))
}

Then update the list of acceptable response formats in your controller:

class BookController extends RestfulController<Book> {
    static responseFormats = ['json', 'xml', 'book', 'bookv2']

    // ...
}

Then using the Accept header you can specify which version you need using the Mime Type:

$ curl -i -H "Accept: application/vnd.books.org.book+json;v=1.0" -X GET http://localhost:8080/books

10.5 Implementing REST controllers

The Resource transformation is a quick way to get started, but typically you’ll want to customize the controller logic, the rendering of the response or extend the API to include additional actions.

10.5.1 Extending the RestfulController super class

The easiest way to get started doing so is to create a new controller for your resource that extends the grails.rest.RestfulController super class. For example:

class BookController extends RestfulController<Book> {
    static responseFormats = ['json', 'xml']
    BookController() {
        super(Book)
    }
}

To customize any logic you can just override the appropriate action. The following table provides the names of the action names and the URIs they map to:

HTTP Method URI Controller Action

GET

/books

index

GET

/books/create

create

POST

/books

save

GET

/books/${id}

show

GET

/books/${id}/edit

edit

PUT

/books/${id}

update

PATCH

/books/${id}

patch

DELETE

/books/${id}

delete

The create and edit actions are only needed if the controller exposes an HTML interface.

As an example, if you have a nested resource then you would typically want to query both the parent and the child identifiers. For example, given the following URL mapping:

"/authors"(resources:'author') {
    "/books"(resources:'book')
}

You could implement the nested controller as follows:

class BookController extends RestfulController<Book> {
    static responseFormats = ['json', 'xml']
    BookController() {
        super(Book)
    }

    @Override
    protected Book queryForResource(Serializable id) {
        Book.where {
            id == id && author.id == params.authorId
        }.find()
    }

}

The example above subclasses RestfulController and overrides the protected queryForResource method to customize the query for the resource to take into account the parent resource.

Customizing Data Binding In A RestfulController Subclass

The RestfulController class contains code which does data binding for actions like save and update. The class defines a getObjectToBind() method which returns a value which will be used as the source for data binding. For example, the update action does something like this…​

class RestfulController<T> {

    def update() {
        T instance = // retrieve instance from the database...

        instance.properties = getObjectToBind()

        // ...
    }

    // ...
}

By default the getObjectToBind() method returns the request object. When the request object is used as the binding source, if the request has a body then the body will be parsed and its contents will be used to do the data binding, otherwise the request parameters will be used to do the data binding. Subclasses of RestfulController may override the getObjectToBind() method and return anything that is a valid binding source, including a Map or a DataBindingSource. For most use cases binding the request is appropriate but the getObjectToBind() method allows for changing that behavior where desired.

Using custom subclass of RestfulController with Resource annotation

You can also customize the behaviour of the controller that backs the Resource annotation.

The class must provide a constructor that takes a domain class as its argument. The second constructor is required for supporting Resource annotation with readOnly=true.

This is a template that can be used for subclassed RestfulController classes used in Resource annotations:

class SubclassRestfulController<T> extends RestfulController<T> {
    SubclassRestfulController(Class<T> domainClass) {
        this(domainClass, false)
    }

    SubclassRestfulController(Class<T> domainClass, boolean readOnly) {
        super(domainClass, readOnly)
    }
}

You can specify the super class of the controller that backs the Resource annotation with the superClass attribute.

import grails.rest.*

@Resource(uri='/books', superClass=SubclassRestfulController)
class Book {

    String title

    static constraints = {
        title blank:false
    }
}

10.5.2 Implementing REST Controllers Step by Step

If you don’t want to take advantage of the features provided by the RestfulController super class, then you can implement each HTTP verb yourself manually. The first step is to create a controller:

$ grails create-controller book

Then add some useful imports and enable readOnly by default:

import grails.gorm.transactions.*
import static org.springframework.http.HttpStatus.*
import static org.springframework.http.HttpMethod.*

@Transactional(readOnly = true)
class BookController {
    ...
}

Recall that each HTTP verb matches a particular Grails action according to the following conventions:

HTTP Method URI Controller Action

GET

/books

index

GET

/books/${id}

show

GET

/books/create

create

GET

/books/${id}/edit

edit

POST

/books

save

PUT

/books/${id}

update

PATCH

/books/${id}

patch

DELETE

/books/${id}

delete

The create and edit actions are already required if you plan to implement an HTML interface for the REST resource. They are there in order to render appropriate HTML forms to create and edit a resource. They can be discarded if that is not a requirement.

The key to implementing REST actions is the respond method introduced in Grails 2.3. The respond method tries to produce the most appropriate response for the requested content type (JSON, XML, HTML etc.)

Implementing the 'index' action

For example, to implement the index action, simply call the respond method passing the list of objects to respond with:

def index(Integer max) {
    params.max = Math.min(max ?: 10, 100)
    respond Book.list(params), model:[bookCount: Book.count()]
}

Note that in the above example we also use the model argument of the respond method to supply the total count. This is only required if you plan to support pagination via some user interface.

The respond method will, using Content Negotiation, attempt to reply with the most appropriate response given the content type requested by the client (via the ACCEPT header or file extension).

If the content type is established to be HTML then a model will be produced such that the action above would be the equivalent of writing:

def index(Integer max) {
    params.max = Math.min(max ?: 10, 100)
    [bookList: Book.list(params), bookCount: Book.count()]
}

By providing an index.gsp file you can render an appropriate view for the given model. If the content type is something other than HTML then the respond method will attempt to lookup an appropriate grails.rest.render.Renderer instance that is capable of rendering the passed object. This is done by inspecting the grails.rest.render.RendererRegistry.

By default there are already renderers configured for JSON and XML, to find out how to register a custom renderer see the section on "Customizing Response Rendering".

Implementing the 'show' action

The show action, which is used to display and individual resource by id, can be implemented in one line of Groovy code (excluding the method signature):

def show(Book book) {
    respond book
}

By specifying the domain instance as a parameter to the action Grails will automatically attempt to lookup the domain instance using the id parameter of the request. If the domain instance doesn’t exist, then null will be passed into the action. The respond method will return a 404 error if null is passed otherwise once again it will attempt to render an appropriate response. If the format is HTML then an appropriate model will produced. The following action is functionally equivalent to the above action:

def show(Book book) {
    if(book == null) {
        render status:404
    }
    else {
        return [book: book]
    }
}

Implementing the 'save' action

The save action creates new resource representations. To start off, simply define an action that accepts a resource as the first argument and mark it as Transactional with the grails.gorm.transactions.Transactional transform:

@Transactional
def save(Book book) {
    ...
}

Then the first thing to do is check whether the resource has any validation errors and if so respond with the errors:

if(book.hasErrors()) {
    respond book.errors, view:'create'
}
else {
    ...
}

In the case of HTML the 'create' view will be rendered again so the user can correct the invalid input. In the case of other formats (JSON, XML etc.), the errors object itself will be rendered in the appropriate format and a status code of 422 (UNPROCESSABLE_ENTITY) returned.

If there are no errors then the resource can be saved and an appropriate response sent:

book.save flush:true
    withFormat {
        html {
            flash.message = message(code: 'default.created.message', args: [message(code: 'book.label', default: 'Book'), book.id])
            redirect book
        }
        '*' { render status: CREATED }
    }

In the case of HTML a redirect is issued to the originating resource and for other formats a status code of 201 (CREATED) is returned.

Implementing the 'update' action

The update action updates an existing resource representation and is largely similar to the save action. First define the method signature:

@Transactional
def update(Book book) {
    ...
}

If the resource exists then Grails will load the resource, otherwise null is passed. In the case of null, you should return a 404:

if(book == null) {
        render status: NOT_FOUND
    }
    else {
        ...
    }

Then once again check for errors validation errors and if so respond with the errors:

if(book.hasErrors()) {
    respond book.errors, view:'edit'
}
else {
    ...
}

In the case of HTML the 'edit' view will be rendered again so the user can correct the invalid input. In the case of other formats (JSON, XML etc.) the errors object itself will be rendered in the appropriate format and a status code of 422 (UNPROCESSABLE_ENTITY) returned.

If there are no errors then the resource can be saved and an appropriate response sent:

book.save flush:true
withFormat {
    html {
        flash.message = message(code: 'default.updated.message', args: [message(code: 'book.label', default: 'Book'), book.id])
        redirect book
    }
    '*' { render status: OK }
}

In the case of HTML a redirect is issued to the originating resource and for other formats a status code of 200 (OK) is returned.

Implementing the 'delete' action

The delete action deletes an existing resource. The implementation is largely similar to the update action, except the delete() method is called instead:

book.delete flush:true
withFormat {
    html {
        flash.message = message(code: 'default.deleted.message', args: [message(code: 'Book.label', default: 'Book'), book.id])
        redirect action:"index", method:"GET"
    }
    '*'{ render status: NO_CONTENT }
}

Notice that for an HTML response a redirect is issued back to the index action, whilst for other content types a response code 204 (NO_CONTENT) is returned.

10.5.3 Generating a REST controller using scaffolding

To see some of these concepts in action and help you get going, the Scaffolding plugin, version 2.0 and above, can generate a REST ready controller for you, simply run the command:

$ grails generate-controller <<Domain Class Name>>

10.6 Calling REST Services with HttpClient

Calling Grails REST services - as well as third-party services - is very straightforward using the Micronaut HTTP Client. This HTTP client has both a low-level API and a higher level AOP-driven API, making it useful for both simple requests as well as building declarative, type-safe API layers.

To use the Micronaut HTTP client you must have the micronaut-http-client and micronaut-serde-jackson dependencies on your classpath. Add the following dependency to your build.gradle file.

build.gradle
implementation "io.micronaut:micronaut-http-client:4.6.6"
implementation "io.micronaut.serde:micronaut-serde-jackson:2.11.0"

Low-level API

The HttpClient interface forms the basis for the low-level API. This interfaces declares methods to help ease executing HTTP requests and receive responses.

The majority of the methods in the HttpClient interface returns Reactive Streams Publisher instances, and a sub-interface called RxHttpClient is included that provides a variation of the HttpClient interface that returns RxJava Flowable types. When using HttpClient in a blocking flow, you may wish to call toBlocking() to return an instance of BlockingHttpClient.

There are a few ways by which you can obtain a reference to a HttpClient. The most simple way is using the create method

Creating an HTTP client
    List<Album> searchWithApi(String searchTerm) {
        String baseUrl = "https://itunes.apple.com/"

        BlockingHttpClient client = HttpClient.create(baseUrl.toURL()).toBlocking() (1)

        HttpRequest request = HttpRequest.GET("/search?limit=25&media=music&entity=album&term=${searchTerm}")
        HttpResponse<String> resp = client.exchange(request, String)
        client.close() (2)

        String json = resp.body()
        ObjectMapper objectMapper = new ObjectMapper() (3)
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        SearchResult searchResult = objectMapper.readValue(json, SearchResult)
        searchResult.results
    }
1 Create a new instance of HttpClient with the create method, and convert to an instance of BlockingHttpClient with toBlocking(),
2 The client should be closed using the close method to prevent thread leaking.
3 Jackson’s ObjectMapper API can be used to map the raw JSON to POGOs, in this case SearchResult

Consult the Http Client section of the Micronaut user guide for more information on using the HttpClient low-level API.

Declarative API

A declarative HTTP client can be written by adding the @Client annotation to any interface or abstract class. Using Micronaut’s AOP support (see the Micronaut user guide section on Introduction Advice), the abstract or interface methods will be implemented for you at compilation time as HTTP calls. Declarative clients can return data-bound POGOs (or POJOs) without requiring special handling from the calling code.

package example.grails

import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client


@Client("https://start.grails.org")
interface GrailsAppForgeClient {

    @Get("/{version}/profiles")
    List<Map> profiles(String version)
}

Note that HTTP client methods are annotated with the appropriate HTTP method, such as @Get or @Post.

To use a client like the one in the above example, simply inject an instance of the client into any bean using the @Autowired annotation.

  @Autowired GrailsAppForgeClient appForgeClient

    List<Map> profiles(String grailsVersion) {
        respond appForgeClient.profiles(grailsVersion)
    }

For more details on writing and using declarative clients, consult the Http Client section of the Micronaut user guide.

Spring HTTP Interface Client

Spring Framework 7 can create an HTTP client from an interface annotated with @HttpExchange and @GetExchange. This is a Spring API, not a Grails DSL or a controller contract.

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;

@HttpExchange("/books")
public interface BookClient {

    @GetExchange("/{id}")
    Book findById(@PathVariable Long id);
}

For a synchronous client, create a proxy with RestClient:

import org.springframework.web.client.RestClient;
import org.springframework.web.client.support.RestClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

RestClient restClient = RestClient.builder()
        .baseUrl("https://example.com")
        .build();
BookClient bookClient = HttpServiceProxyFactory
        .builderFor(RestClientAdapter.create(restClient))
        .build()
        .createClient(BookClient.class);

Spring 7 can also register the proxy as a bean. Group related interfaces and configure their shared RestClient builder:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
import org.springframework.web.service.registry.HttpServiceGroup.ClientType;
import org.springframework.web.service.registry.ImportHttpServices;

@Configuration
@ImportHttpServices(group = "books", types = BookClient.class,
        clientType = ClientType.REST_CLIENT)
class BookClientConfiguration {

    @Bean
    RestClientHttpServiceGroupConfigurer bookClientConfigurer() {
        return groups -> groups.filterByName("books")
                .forEachClient((group, builder) -> builder.baseUrl("https://example.com"));
    }
}

The registered BookClient can then be constructor-injected into an application bean and called like any other interface. Synchronous methods can return a decoded body, HttpHeaders, or ResponseEntity.

For Mono or Flux return values, add Spring WebFlux to the classpath and use WebClient instead. Register the group with clientType = ClientType.WEB_CLIENT and configure it through WebClientHttpServiceGroupConfigurer; reactive applications also need Reactor types on the classpath.

HTTP interface parameters cannot be null unless the parameter is optional. By default, 4xx and 5xx responses raise an exception. Configure status handling on the underlying RestClient or WebClient when an API requires different behavior.

10.7 The REST Profile

Grails supports a tailored profile for creating REST applications that provides a more focused set of dependencies and commands.

To get started with the REST profile using the Grails Shell CLI, create an application specifying rest-api as the name of the profile:

$ grails create-app my-api --profile rest-api

This will create a new REST application that provides the following features:

  • Default set of commands for creating and generating REST endpoints

  • Defaults to using JSON views for rendering responses (see the next section)

  • Fewer plugins than the default Grails web profile (no GSP, no Asset Pipeline, nothing HTML related)

You will notice for example in the grails-app/views directory that there are *.gson files for rendering the default index page and as well as any 404 and 500 errors.

If you issue the following set of commands:

$ grails create-domain-class my.api.Book
$ grails generate-all my.api.Book

Instead of CRUD HTML interface a REST endpoint is generated that produces JSON responses. In addition, the generated functional and unit tests by default test the REST endpoint.

To generate a REST application using the Forge CLI, use the following:

$ grails -t forge create-restapi my-api

This will create a new REST application with the same focused setup as above:

  • Default set of commands for creating and generating REST endpoints

  • Defaults to using JSON views for rendering responses (see the next section)

  • Fewer plugins than a default Grails Web-style application (no GSP, no Asset Pipeline, nothing HTML related)

You will notice for example in the grails-app/views directory that there are *.gson files for rendering the default index page and as well as any 404 and 500 errors.

If you issue the following set of commands:

$ grails create-domain-class my.api.Book
$ ./gradlew runCommand -Pargs="generate-all my.api.Book"
The generate-* commands are only available after adding the org.apache.grails:grails-scaffolding dependency to your project. They are not available by default in a REST application. Also, they will no longer produce *.gson files as that was a feature of the REST API-profile.

Instead of CRUD HTML interface a REST endpoint is generated that produces JSON responses. In addition, the generated functional and unit tests by default test the REST endpoint.

10.8 OpenAPI Descriptions

Grails applications can publish an OpenAPI description of their REST endpoints. The optional grails-openapi module derives it from the application itself - its URL mappings, its controllers, its domain classes and command objects, and their constraints - and lets the standard OpenAPI annotations correct or enrich what is derived.

The description can be produced two ways, from the same configuration:

  • At build time, by the generate-open-api command, which writes it to a file. The file can be packaged and served as a static resource, reviewed in a change, or handed to a client code generator, and the running application exposes nothing it does not choose to serve.

  • At runtime, by springdoc-openapi, which serves it at /v3/api-docs and can serve Swagger UI beside it. springdoc builds its document from Spring MVC handler methods, which Grails does not use, so the module contributes the Grails endpoints to it.

Getting Started

dependencies {
    implementation 'org.apache.grails:grails-openapi'
}

That is all the build-time command needs. To serve the description at runtime as well, add springdoc, and optionally Swagger UI:

dependencies {
    // the document, served at /v3/api-docs
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api'

    // or the document and Swagger UI, served at /swagger-ui/index.html
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui'
}

Generating the Description at Build Time

./gradlew generateOpenApi

The command starts the application context, without a web server, and writes the default document to build/openapi/openapi.yaml and each group to build/openapi/openapi-<group>.yaml. A character of the group’s name a file name cannot hold, such as the / of admin/v1, is written as -, so that group is written to openapi-admin-v1.yaml; where two groups would then be written to one file, the command writes nothing and names them. The --output-directory and --format options, or the grails.openapi.output-directory and grails.openapi.output-format settings, choose where and whether YAML or JSON is written:

./gradlew runCommand -Pargs="generate-open-api --format=json --output-directory=build/api"

Because the command starts the application, it needs whatever the application needs to start in the environment it runs in, such as a datasource.

Where springdoc is configured, the command applies the method filters, operation customizers and OpenAPI customizers springdoc applies to the documents it serves, so each file describes what springdoc serves. That holds only in an environment where springdoc is enabled: with springdoc.api-docs.enabled: false, as an application generated with the openapi feature has in production, springdoc configures nothing, so the command writes no springdoc.group-configs group and applies none of springdoc’s filters and customizers, and says so; grails.openapi.groups are written as ever. A filter or customizer that fails there, such as one that reads the current request, is skipped and logged, rather than the operations it was applied to.

To package the description and serve it as a static file, include it in the application’s static resources when the application is assembled:

def openApi = layout.buildDirectory.dir('openapi')

tasks.named('bootJar') {
    dependsOn 'generateOpenApi'
    from(openApi) { into 'BOOT-INF/classes/static/api' }
}

It is then served at /static/api/openapi.yaml, and any viewer can render it. For example, Redoc needs only a page that points at the file:

<redoc spec-url="/static/api/openapi.yaml"></redoc>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>

Serving the Description at Runtime

With springdoc on the classpath no further configuration is required. springdoc registers its own resource handlers, so Swagger UI is served even though Grails leaves Spring Boot’s catch-all resource handler disabled, and the springdoc paths fall through the Grails URL mappings because they resolve to no controller.

The Grails operations are added to each document before any other springdoc customizer runs, so an OpenApiCustomizer bean, a GlobalOpenApiCustomizer bean, or a customizer a GroupedOpenApi adds sees them and can change them. springdoc runs an OpenApiLocaleCustomizer before all of those, though, so one does not see the Grails operations, and the generate-open-api command, which answers no request in a locale, does not run one.

springdoc describes the application’s own Spring MVC endpoints as it does without Grails, since Jackson rather than Grails renders what they return: a domain class one of them returns is described with everything Jackson writes of it, such as a getter with nothing persisted behind it, and with the whole of what it is associated with. Where a Grails endpoint in the same document uses the class too, it is described once, as Grails renders it, and the Spring MVC endpoint refers to that schema as well. A class springdoc describes under the same name as another the Grails endpoints use is named apart from it, as below, in each document on its own.

An application whose URL mappings include a catch-all that resolves to a view or a controller - a single page application forwarding unmatched paths to an index view, or an API answering unmatched paths with a 404 of its own - needs nothing more either: the paths springdoc serves are excluded from the URL mappings, at springdoc.api-docs.path and springdoc.swagger-ui.path where they are configured, while springdoc serves them.

The runtime description is served to anyone who can reach the application, and describes every endpoint, resource and constraint it documents. Secure /v3/api-docs/ and /swagger-ui/ the way the application secures anything else, or disable them where they should not be served, for example in production with springdoc.api-docs.enabled: false and springdoc.swagger-ui.enabled: false, which an application generated with the openapi feature sets for production. An application that only needs the description as a file can generate it at build time and leave springdoc out altogether. With the Spring Security plugin, whose default is to reject a request no rule matches, the paths need a rule of their own, such as an entry in grails.plugin.springsecurity.controllerAnnotations.staticRules.

Configuration

Setting Default Meaning

grails.openapi.enabled

true

Whether the description is generated at all

grails.openapi.base-document

A YAML or JSON document the description starts from, see The Base Document

grails.openapi.display-name

info.app.name

The title of the default document

grails.openapi.annotated-only

false

Whether only annotated actions are described, see Choosing What Is Described

grails.openapi.include-form-actions

false

Whether the create and edit actions of a RestfulController are described

grails.openapi.paths-to-match, grails.openapi.paths-to-exclude

Ant patterns of the paths the default document describes, or leaves out

grails.openapi.packages-to-scan, grails.openapi.packages-to-exclude

The packages of the controllers the default document describes, or leaves out

grails.openapi.produces-to-match, grails.openapi.consumes-to-match

The media types an operation the default document describes must produce, or consume

grails.openapi.headers-to-match

The header conditions an operation must declare, such as Accept-Version=1.0

grails.openapi.groups.<name>.*

A group and what it selects, see Groups

grails.openapi.output-directory

build/openapi

Where generate-open-api writes

grails.openapi.output-format

yaml

Whether generate-open-api writes yaml or json

Where springdoc is used, its own springdoc.paths-to-match, springdoc.paths-to-exclude, springdoc.packages-to-scan, springdoc.packages-to-exclude, springdoc.produces-to-match, springdoc.consumes-to-match and springdoc.headers-to-match apply to the Grails endpoints of the default document too, and springdoc.api-docs.version chooses between OpenAPI 3.1, the default, and 3.0 for both the build-time and the runtime description.

Choosing What Is Described

By default every action a URL mapping reaches is described. An application whose published API is only part of what it serves can narrow that down:

  • @Hidden on a controller or an action, or @Operation(hidden = true), withholds it.

  • grails.openapi.annotated-only: true describes only an action that declares @Operation, or whose controller declares @Tag, so the published API is exactly what the application annotated.

  • The path and package settings above select by where an endpoint is served and which controller serves it:

grails:
    openapi:
        paths-to-match: /api/**
        packages-to-exclude: com.example.internal

The media type settings select the way springdoc selects a Spring MVC handler method: an operation is described only where it produces, or consumes, exactly the media types a setting lists. An operation produces the media types of its controller’s responseFormats, application/json where it declares none, and consumes the same media types where it binds a body. A mapping declared for a version declares the Accept-Version header it is matched on, so headers-to-match: Accept-Version=1.0 selects the operations of version 1.0. Any other header criterion leaves every Grails endpoint out, as it leaves out a Spring handler method that declares no header condition:

grails:
    openapi:
        produces-to-match: application/json

Where springdoc is used, its method filters decide for the Grails actions too. Each filter is called with the method the action is declared as, and an action a filter excludes is left out, at runtime and by the generate-open-api command. An OpenApiMethodFilter bean applies to the default document, a GlobalOpenApiMethodFilter bean to every document, and a filter a GroupedOpenApi adds to that group:

@Bean
OpenApiMethodFilter internalActionFilter() {
    { Method action -> !action.isAnnotationPresent(Internal) } as OpenApiMethodFilter
}

The create and edit actions of a RestfulController answer the forms an HTML client renders, so they are left out unless grails.openapi.include-form-actions is set.

Groups

A group is a document of its own, describing part of the application for one audience:

grails:
    openapi:
        groups:
            sales:
                display-name: Sales API
                paths-to-match: /api/v1/**
            depots:
                display-name: Depot Lifecycle API
                paths-to-match: /api/v2/**

Each group takes the same paths-to-match, paths-to-exclude, packages-to-scan, packages-to-exclude, produces-to-match, consumes-to-match and headers-to-match settings as the default document, and display-name titles it. The generate-open-api command writes each group to a file of its own, and at runtime springdoc serves each at /v3/api-docs/<name>, applying the same criteria to any Spring MVC endpoints the application also has. A group declared to springdoc, in springdoc.group-configs or as a GroupedOpenApi bean, is honored the same way, both at runtime and by the command. Where springdoc is used, its own top-level criteria, such as springdoc.paths-to-match, apply to every group ahead of the group’s own, criterion by criterion, as springdoc applies them to its Spring MVC endpoints. A group name declared more than once is described by its first declaration, and logged, since springdoc may select the group’s Spring MVC endpoints by another:

springdoc:
    group-configs:
        - group: sales
          paths-to-match: /api/v1/**

The Base Document

What describes the API as a whole - its title and description, its servers, how it is secured, the descriptions of its tags, vendor extensions for a viewer, and endpoints the application does not map itself, such as a login endpoint a security plugin provides - is written once, in a base document the description starts from:

grails:
    openapi:
        base-document: classpath:openapi-base.yml
src/main/resources/openapi-base.yml
openapi: 3.1.0
info:
  title: Sales API
  description: |
    Inventory, orders and invoices for sales customers.
servers:
  - url: https://api.example.com
security:
  - Bearer: []
tags:
  - name: orders
    description: Retrieving and creating orders
components:
  securitySchemes:
    Bearer:
      type: http
      scheme: bearer
      bearerFormat: JWT

The information, servers, security and external documentation of the base document are used as they are. Its tags, extensions and components are kept beside what is derived, and so are its paths, in each document whose path settings select them. Where the base document declares no version, the application’s info.app.version is used, and without a base document the description is titled with info.app.name.

What Is Documented

Every URL mapping that names a controller statically becomes a path. A resources mapping contributes each of its generated HTTP methods:

class UrlMappings {
    static mappings = {
        "/books"(resources: 'book')
    }
}

produces GET and POST on /books, and GET, PUT, PATCH and DELETE on /books/{id}, with POST, which Grails maps to update as well, where the controller’s allowedMethods allows it.

A RestfulController constructed read only answers 405 from its write actions, so only what it serves is described:

class PublisherController extends RestfulController<Publisher> {

    PublisherController() {
        super(Publisher, true)
    }
}

mapped with "/publishers"(resources: 'publisher'), produces only GET on /publishers and /publishers/{id}. A write action the controller overrides is described, because it does whatever the override does.

URL variables become OpenAPI path parameters, so "/books/$id" is documented as /books/{id}. The optional .format extension Grails appends to REST mappings is omitted, because OpenAPI describes response formats through content types rather than the path. An OpenAPI path parameter is always required, so a mapping with an optional variable, such as "/photos/$id?", is documented at both /photos/{id} and /photos. A mapping with a wildcard that captures nothing, such as a catch-all "/**", cannot be written as an OpenAPI path and is not documented.

A mapping that names a controller but no action is documented as the controller’s default action. One that takes the action from the path, such as "/topics/$action"(controller: 'topic'), is documented for each action the controller declares, and, where the action is optional, as "/topics/$action?" makes it, at the path without it as the controller’s default action. One that chooses the action by the method of the request, such as action: [GET: 'show', DELETE: 'delete'], is documented for each method. A mapping whose action is decided by a closure as each request is made is skipped, and logged.

A mapping declared for an HTTP method is documented for that method, unless the controller’s allowedMethods refuses it for the action, which Grails answers with 405. A mapping that accepts any method is documented for the methods allowedMethods declares for the action, or, where it declares none, for the method a RestfulController action of that name answers, and GET for any other action. A mapping that names a namespace is documented with that namespace’s controller, so versioned controllers of the same name are each described at their own routes.

Versions

A mapping declared for a version is matched on the Accept-Version header a request sends:

"/books"(version: '1.0', resources: 'book', namespace: 'v1')
"/books"(version: '2.0', resources: 'book', namespace: 'v2')

A request asking for no version is answered by the highest version, so that is the one the default document describes, with an optional Accept-Version parameter naming it. Grails also takes the version from a v parameter of the Accept media type, which is not described. A group describes another version by selecting its header, and there the parameter is required:

grails:
    openapi:
        groups:
            v1:
                headers-to-match: Accept-Version=1.0

Resource Schemas

A RestfulController is described as serving the type it declares: BookController extends RestfulController<Book> responds with, and accepts, a Book, whatever the controller is named. The type may be a domain class or any other class, such as a command object the controller renders in place of the domain class. A controller that is not a RestfulController declares no response type, so its responses are described without a body unless an annotation declares one.

A type is described in components/schemas through swagger-core, so a @Schema or Jackson annotation on the class or on one of its properties is honored. The metaClass every Groovy object has and the errors a validateable object has are not part of it.

A property of a class the Grails operations are described with - a domain class, a command object, or any other class, such as one an @ApiResponse names - is described by the name Grails' converters, JSON views and data binding render and bind it by: its own. So neither a @JsonProperty rename, which Grails ignores, nor the name Jackson would otherwise write, such as isbn for ISBN, changes it, and a command object’s request parameters are described by the names Grails binds them by. Where the description should name a property otherwise, @Schema(name) renames it, and its constraints, and whether it is required or read only, follow the rename. A class only springdoc’s own Spring MVC endpoints use keeps the names Jackson writes, since Jackson renders it there.

A schema is named after its class, or after the name a @Schema annotation on the class declares. Classes sharing a name in different packages are each named by their package as well, such as com.example.v1.Book and com.example.v2.Book, so neither stands in for the other and the names do not depend on which is described first. A class sharing the name of a schema the base document declares, or one the document derives, such as ValidationErrors or a Patch schema, is named by its package in the same way. Only a class described as a schema of its own takes a name: an enum is described where it is used unless @Schema(enumAsRef = true) describes it as a schema of its own, and a class described in the place of another - the implementation a @Schema annotation names, or the value a @JsonValue accessor returns - leaves the name to the class described.

import io.swagger.v3.oas.annotations.media.Schema

@Schema(description = 'A book in the catalog')
class Book {

    @Schema(description = 'Full title as printed', example = 'Dune')
    String title

    static constraints = {
        title blank: false, nullable: false, maxSize: 255
    }
}

The declared constraints of a domain class or a Validateable class are applied over that result, because swagger-core cannot see them. The two combine, so the annotation above supplies the prose and the constraints block the validation:

"title": {
  "type": "string",
  "description": "Full title as printed",
  "example": "Dune",
  "maxLength": 255
}

The constraints are carried across as follows:

Constraint OpenAPI

nullable: false

listed in required

nullable: true

nullable in OpenAPI 3.0, a null type in 3.1, and null in any enum

blank: false

minLength of 1

maxSize / minSize / size of a string

maxLength and minLength

maxSize / minSize / size of a collection

maxItems and minItems

min / max / range

minimum and maximum

inList

enum

matches

pattern, anchored, since Grails matches the whole value

email / url

format

Where the annotation and the constraints both say whether a property must be sent, the property’s @Schema wins: a property it declares nullable = true or requiredMode = NOT_REQUIRED is not listed in required, and one it declares requiredMode = REQUIRED is, whatever it is constrained to. So a Validateable class that declares no constraints, whose every property Grails constrains nullable: false by default, still describes the properties it annotates as optional that way.

A domain class is described as Grails renders it: its identifier and its persistent properties. A transient, or a getter with nothing persisted behind it, is not rendered and is not described. The version is not rendered either unless it is configured to be, and is described only then: in JSON with grails.converters.json.domain.include.version, or grails.converters.domain.include.version for every format. The document describes the version where JSON renders it, so a version rendered in XML alone, with grails.converters.xml.domain.include.version, is not described.

A property data binding does not bind is marked readOnly: the identifier and the version, which the server assigns, dateCreated and lastUpdated, a property constrained bindable: false, and a property a command object can only read. Tools that understand OpenAPI leave a read-only property out of a request body, so one schema describes both directions.

A property described by a reference to another schema, such as an embedded class, keeps what is said of it, being nullable, whether its constraint or its @Schema says so, or read only, or the description, deprecation or access mode @Schema gives it. OpenAPI 3.1 says so beside the $ref, and a nullable one is oneOf the reference and a null type. OpenAPI 3.0 ignores anything beside a $ref, so there the property is allOf the reference, and says it on that.

An association to another domain class is described by the associated identifier, which is what Grails renders for it and what it binds it from:

"author": {
  "type": "object",
  "properties": { "id": { "type": "integer", "format": "int64" } },
  "required": ["id"]
}

A to-many association is an array of those references, and an embedded association is described in full.

A class that cannot be introspected is left out of the document and logged, rather than failing the document, and an operation that referred to it is described without a shape rather than with a reference that does not resolve. A class whose constraints cannot be evaluated is described without them, and logged.

Request Bodies

An operation that accepts a body describes the type the action binds. Where the action takes a command object that is the command, in preference to the resource the controller serves:

class OrdersController {

    def submit(OrderCommand cmd) { }
}

Where one of the actions RestfulController declares takes no command object, the body is the resource the controller serves, whether the controller inherits the action or overrides it. Any other action binds a body only where it takes a command object or declares one. A patch binds only what it is sent, so its body is a copy of the resource’s schema, named for it with a Patch suffix, that requires nothing. A body with a property a file is bound to, a MultipartFile or a Part, is described as multipart/form-data, with the file as a binary string. An action that binds something else declares it with @RequestBody, which replaces what is derived.

Responses

The actions RestfulController declares are described by the part each plays in the resource, so the statuses are the ones it answers with rather than a uniform 200. That holds for an action the controller overrides as much as for one it inherits: an override is how an action is annotated, and it still lists, shows, creates, updates or deletes the resource:

Action Status Body

index

200

a collection of the resource

show, edit

200, 404

the resource

create

200

the resource

save

201, 422

the resource

update, patch

200, 404, 422

the resource

delete

204, 404

none

Any other action of the controller, such as a search, is described as any controller’s action is: a 200, a 404 where its path has a variable, and whatever its annotations declare.

A 422 answers with the validation errors Grails renders for a request that cannot be bound, described once as the ValidationErrors schema, or as grails.validation.ValidationErrors, after the class Grails renders them from, where the document already describes something else as ValidationErrors, such as a class of the application a Spring MVC endpoint returns. The converters render them, in JSON as an object listing them and in XML as an errors element holding an error element for each. Where a JSON view renders them, as the errors view of an application generated with JSON views does, they are described in JSON as that view renders them: one error with its message, path and link, or several embedded with their total. The status is the one the view sets, which is 422 for the errors view an application is generated with. So:

  • A controller with an errors view of its own, such as grails-app/views/book/_errors.gson, is described answering 422 in JSON without a shape, since only the application knows it; the view answers with the status it sets, as the errors view it is usually copied from sets 422.

  • An application with JSON views but no errors view renders the errors with the view for any object, which answers with success rather than 422, so its 422 is described in XML alone.

  • An application rendering its errors another way declares ValidationErrors in the base document, which then describes them in every media type, or describes an action’s errors with @ApiResponse.

A response and a request body are described in the media types of the formats the controller declares in responseFormats, for the action or for every action, and in application/json where it declares none. A format is described as the first media type the application configures for it in grails.mime.types:

class BookController extends RestfulController<Book> {

    static responseFormats = ['json', 'xml']
}

describes each body as application/json and as text/xml, the first media type Grails configures for xml by default. Only a data format, json or xml, carries the shape described; a format that renders a view, a form or a HAL document, such as html or hal, is listed without one. A request body is described in the data formats alone.

The XML is described as the converters render it, with the OpenAPI xml object: a domain class or a command object as an element named for its class, such as book, its identifier as the id attribute and its version as an attribute, an association as an element carrying the associated identifier as the id attribute, a collection of values as an element holding one named for the class of each, such as <lines><string>a</string></lines>, and a listing as a list element holding one for each. The xml object cannot describe a map, which Grails renders as entry elements keyed by an attribute, and the document describes the XML Grails renders by default, not that of grails.converters.xml.default.deep or grails.converters.xml.domain.include.class.

RestfulController answers its own save and update with a Location header naming the resource, which the 201 and 200 responses describe, and its patch too, which update serves. The header is something only RestfulController’s code sends, rather than part of what the action answers by its role, since a controller generated for a REST application does not send it. So an action the controller overrides is described without it, and one that sends it declares it with `@ApiResponse(headers).

Parameters

A listing accepts the paging and sorting RestfulController passes to GORM, so those are described on it:

Parameter Meaning

max

the most results to return, capped at 100, defaulting to 10

offset

the result to start from

sort

the property to sort by

order

asc or desc

An index the controller overrides is described with them too. One that does not page withdraws them, as it would any parameter derived for it:

@Parameter(name = 'max', hidden = true)
@Parameter(name = 'offset', hidden = true)
@Parameter(name = 'sort', hidden = true)
@Parameter(name = 'order', hidden = true)
@Override
Object index(Integer max) {
    respond bookService.featured()
}

The identifier an operation of a RestfulController addresses is described with the type the domain class declares for it:

Identifier OpenAPI

Long

integer, format int64

Integer

integer, format int32

UUID

string, format uuid

String or any other type

string

An association to the domain class is described with the same type. A @Parameter declared for the identifier refines it, and keeps that type unless its schema names another.

An action parameter of a simple type is bound from the request by name, so it is described as a query parameter of that type:

def lookup(String query, Integer limit) { }

This needs the parameter names, which Grails keeps by default through the preserveParameterNames setting of the Grails Gradle plugin. A parameter renamed with @RequestParameter is described by the name a request sends. A parameter an action reads from params rather than declaring is described with @Parameter. A parameter Grails does not bind - one declared as Object, an interface or an abstract class - is not described.

A command object an action binds on a request without a body, such as a GET, is bound from the request parameters, so each property it binds that a request parameter can carry is described as a query parameter, with its constraints:

def search(BookSearch search) { }

A path variable is described with the type of what it is bound to: the identifier of the resource an operation addresses, the identifier of the resource a nested mapping names it by, such as bookId in /books/{bookId}/chapters, or else the action parameter of the same name. A pattern or a list of values the mapping constrains it to is described too:

"/isbn/$code"(controller: 'book', action: 'byIsbn') {
    constraints {
        code(matches: /\d{13}/)
    }
}

Correcting What Is Derived

What is derived can be corrected or enriched with the standard OpenAPI annotations. They are read by swagger-core, so they mean what they mean on any other endpoint:

Annotation Effect

@Operation

the summary, description, operation id, tags, deprecation, external documentation, parameters, responses, request body and security of an action

@ApiResponse

a response of an action, or, on the controller, of each of its actions; one declared for a status the operation already describes replaces it, and a success status an action declares replaces the success statuses derived for it

@Parameter

a parameter of an action, declared on the action or on one of its parameters; one the operation already describes, such as a path variable, is refined

@RequestBody

the body an action binds

@SecurityRequirement

the security of an action, or, on the controller, of each of its actions

@Tag

the groups a controller’s operations appear under, and their descriptions

@Hidden

withholds a controller or an action

An action answers success with the statuses it declares, so a success status declared on the action, or in its @Operation, replaces any other success status derived for it, with what that said, such as a Location header. A save that queues what it is sent, rather than creating it, and declares @ApiResponse(responseCode = '200', description = 'Queued') is described answering 200, not 201. An error status an action declares is added to those derived, and a status its controller declares is added to each of its actions, and kept beside the success status an action declares.

import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.enums.ParameterIn
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.tags.Tag

@Tag(name = 'orders', description = 'Retrieving and creating orders')
@ApiResponse(responseCode = '401', description = 'Invalid API token')
@ApiResponse(responseCode = '403', description = 'Permission denied')
class OrderController extends RestfulController<OrderCommand> {

    OrderController() {
        super(OrderCommand)
    }

    @Operation(summary = 'Fetch all orders')
    @Parameter(name = 'profile', in = ParameterIn.QUERY,
            description = 'The customer profile to act for',
            schema = @Schema(implementation = Long))
    @Override
    Object index(Integer max) {
        // ...
    }

    @ApiResponse(responseCode = '200', description = 'The CSC letter',
            content = @Content(mediaType = 'application/pdf',
                    schema = @Schema(type = 'string', format = 'binary')))
    def cscLetter() {
        // ...
    }
}

Where springdoc is used, its operation customizers are given each Grails operation too, once the annotations are applied, with a HandlerMethod of the controller and the method the action is declared as: an OperationCustomizer bean for the default document, a GlobalOperationCustomizer bean for every document, and a customizer a GroupedOpenApi adds for that group. A customizer that returns null leaves the operation out. A schema a customizer adds to the components is kept under the name it gives it, and is not named apart from the classes the Grails operations are described with. The generate-open-api command applies them the same way:

@Bean
GlobalOperationCustomizer actionNameCustomizer() {
    { Operation operation, HandlerMethod handlerMethod ->
        operation.addExtension('x-action', "${handlerMethod.beanType.simpleName}.${handlerMethod.method.name}")
        operation
    } as GlobalOperationCustomizer
}

Controllers a Mapping Reaches Without Naming

A REST application maps its controllers without naming them, either by naming the action and leaving the controller to the request, as a generated REST API does:

get  "/$controller(.$format)?"(action: 'index')
post "/$controller(.$format)?"(action: 'save')
get  "/$controller/$id(.$format)?"(action: 'show')

or by leaving both, as the default mapping does:

"/$controller/$action?/$id?(.$format)?" {}

Either way the REST controllers those mappings reach are described, at the URLs the mapping serves: RestfulController subclasses, and controllers declaring the formats they respond in with responseFormats, none of them html. The first form above produces GET /book, POST /book and GET /book/{id}; the second produces GET /book/index, POST /book/save and GET /book/show/{id}, and, because its action is optional, GET /book for the controller’s default action. An application whose mappings name every controller gets neither, because expansion follows the mappings rather than the controllers.

A mapping that captures the namespace, such as "/$namespace/$controller/$action?/$id?", reaches each controller at its own namespace, and does not reach a controller without one. A mapping that leaves the namespace out reaches the controller Grails resolves for the name alone, so of two controllers sharing a name only a namespaced mapping reaches either.

A controller a resources mapping names and a mapping that does not name it are described at both, because both answer. Where that gives two operations the same identifier the second is qualified by the path it is reached at, so no two operations in the document share one. An application wanting only the first form removes the default mapping from its UrlMappings.

A controller rendering views for a browser declares no responseFormats, or declares html among them, such as a login controller responding in html and json, so it does not appear in the document. Where responseFormats is a map by action, each action is decided by its own entry: with [index: ['json'], page: ['html', 'json']], index is described and page, like an action the map leaves out, is not. responseFormats is read as respond reads it, a list, or a list for each action; an array is not one, so respond answers in any configured format, html too. Controllers @Scaffold generates extend RestfulController, and those generate-controller generates for a REST application declare responseFormats, so both are described; those it generates for a web application render views and are not.

A controller that is not a RestfulController but whose save and update actions bind the same domain class, as the controllers generated for a REST application do, answers the actions RestfulController declares as a RestfulController of that class does, so they are described the same way: with its statuses, the domain class as its responses and bodies, and the paging of its listing. Its other actions are described as any controller’s are.

Limitations

  • A mapping that accepts any HTTP method for an action allowedMethods does not restrict is documented as GET, because OpenAPI requires a concrete operation.

  • A mapping declared for an HTTP method OpenAPI has no operation for is skipped, and logged.

  • A RestfulController is known to be read only, and the resource it serves where it declares no type argument, from the controller the application serves requests with, which is not created to be described. So one in a scope other than singleton is described with its write actions, and one that also passes its resource only to the constructor, as extends RestfulController with super(Book) does, is described without a schema for its requests and responses. This applies to every controller where grails.controllers.defaultScope is prototype, rather than only to one declaring its own scope. Declaring the type argument, as extends RestfulController<Book>, describes the resource in any scope.

  • A resource is described as Grails renders a domain class or an object by default. A JSON view, other than the errors view, or a custom renderer that renders another shape is described with @ApiResponse.

  • A date is described as a date-time. Grails binds it with the formats grails.databinding.dateFormats lists, which by default read the offset or Z of a time only when it has three digits of milliseconds, such as 2024-05-01T10:00:00.000+02:00, or an offset written without a colon, such as 2024-05-01T10:00:00+0200. Otherwise, as in 2024-05-01T10:00:00Z or 2024-05-01T10:00:00+02:00, the time is read in the zone of the server, and fewer digits of milliseconds are misread: 2024-05-01T10:00:00.5Z is read as 5 milliseconds.

  • A variable capturing several segments, such as $path**, is described as a path parameter, which OpenAPI allows only one segment; a client must not encode the / it holds.

10.9 JSON Views

As mentioned in the previous section the REST profile by default uses JSON views to render JSON responses. These play a similar role to GSP, but instead are optimized for outputing JSON responses instead of HTML.

You can continue to separate your application in terms of MVC, with the logic of your application residing in controllers and services, whilst view related matters are handled by JSON views.

JSON views also provide the flexibility to easily customize the JSON presented to clients without having to resort to relatively complex marshalling libraries like Jackson or Grails' marshaller API.

Since Grails 3.1, JSON views are considered by the Grails team the best way to present JSON output for the client, the section on writing custom marshallers has been removed from the user guide. If you are looking for information on that topic, see the Grails 3.0.x guide.

10.9.1 Getting Started

If you are using the REST application or REST or AngularJS profiles, then the JSON views plugin will already be included and you can skip the remainder of this section. Otherwise you will need to modify your build.gradle to include the necessary plugin to activate JSON views:

implementation 'org.apache.grails:grails-views-gson:7.0.0' // or whatever is the latest version
The source code repository for JSON views can be found on Github if you are looking for more documentation and contributions

In order to compile JSON views for production deployment you should also activate the Gradle plugin by first modifying the buildscript block:

buildscript {
    ...
    dependencies {
        ...
        classpath platform("org.apache.grails:grails-gradle-bom:{version}")
        classpath "org.apache.grails:grails-gradle-plugins"
    }
}

Then apply the org.apache.grails.views-json Gradle plugin after any Grails core gradle plugins:

...
apply plugin: "org.apache.grails.gradle.grails-web"
apply plugin: "org.apache.grails.views-json"

This will add a compileGsonViews task to Gradle, which is invoked prior to creating the production JAR or WAR file.

10.9.2 Creating JSON Views

JSON views go into the grails-app/views directory and end with the .gson suffix. They are regular Groovy scripts and can be opened in any Groovy editor.

Example JSON view:

json.person {
    name "bob"
}
To open them in the Groovy editor in Intellij IDEA, double click on the file and when asked which file to associate it with, choose "Groovy"

The above JSON view produces:

{"person":{"name":"bob"}}

There is an implicit json variable which is an instance of StreamingJsonBuilder.

Example usages:

json(1,2,3) == "[1,2,3]"
json { name "Bob" } == '{"name":"Bob"}'
json([1,2,3]) { n it } == '[{"n":1},{"n":2},{"n":3}]'

Refer to the API documentation on StreamingJsonBuilder for more information about what is possible.

10.9.3 JSON View Templates

You can define templates starting with underscore _. For example given the following template called _person.gson:

model {
    Person person
}
json {
    name person.name
    age person.age
}

You can render it with a view as follows:

model {
    Family family
}
json {
    name family.father.name
    age family.father.age
    oldestChild g.render(template:"person", model:[person: family.children.max { Person p -> p.age } ])
    children g.render(template:"person", collection: family.children, var:'person')
}

Alternatively for a more concise way to invoke templates, using the tmpl variable:

model {
    Family family
}
json {
    name family.father.name
    age family.father.age
    oldestChild tmpl.person( family.children.max { Person p -> p.age } ] )
    children tmpl.person( family.children )
}

10.9.4 Rendering Domain Classes with JSON Views

Typically your model may involve one or many domain instances. JSON views provide a render method for rendering these.

For example given the following domain class:

class Book {
    String title
}

And the following template:

model {
    Book book
}

json g.render(book)

The resulting output is:

{id:1, title:"The Stand"}

You can customize the rendering by including or excluding properties:

json g.render(book, [includes:['title']])

Or by providing a closure to add additional JSON output:

json g.render(book) {
    pages 1000
}

10.9.5 JSON Views by Convention

There are a few useful conventions you can follow when creating JSON views. For example if you have a domain class called Book, then creating a template located at grails-app/views/book/_book.gson and using the respond method will result in rendering the template:

def show(Long id) {
    respond Book.get(id)
}

In addition if an error occurs during validation by default Grails will try to render a template called grails-app/views/book/_errors.gson, otherwise it will try to render grails-app/views/errors/_errors.gson if the former doesn’t exist.

This is useful because when persisting objects you can respond with validation errors to render these aforementioned templates:

@Transactional
def save(Book book) {
    if (book.hasErrors()) {
        transactionStatus.setRollbackOnly()
        respond book.errors
    }
    else {
        // valid object
    }
}

If a validation error occurs in the above example the grails-app/views/book/_errors.gson template will be rendered.

For more information on JSON views (and Markup views), see the JSON Views user guide.

10.10 Customizing Response Rendering

If you are looking for a more low-level API and JSON or Markup views don’t suite your needs then you may want to consider implementing a custom renderer.

10.10.1 Customizing the Default Renderers

The default renderers for XML and JSON can be found in the grails.rest.render.xml and grails.rest.render.json packages respectively. These use the Grails converters (grails.converters.XML and grails.converters.JSON) by default for response rendering.

You can easily customize response rendering using these default renderers. A common change you may want to make is to include or exclude certain properties from rendering.

Including or Excluding Properties from Rendering

As mentioned previously, Grails maintains a registry of grails.rest.render.Renderer instances. There are some default configured renderers and the ability to register or override renderers for a given domain class or even for a collection of domain classes. To include a particular property from rendering you need to register a custom renderer by defining a bean in grails-app/conf/spring/resources.groovy:

import grails.rest.render.xml.*

beans = {
    bookRenderer(XmlRenderer, Book) {
        includes = ['title']
    }
}
The bean name is not important (Grails will scan the application context for all registered renderer beans), but for organizational and readability purposes it is recommended you name it something meaningful.

To exclude a property, the excludes property of the XmlRenderer class can be used:

import grails.rest.render.xml.*

beans = {
    bookRenderer(XmlRenderer, Book) {
        excludes = ['isbn']
    }
}

Customizing the Converters

As mentioned previously, the default renders use the grails.converters package under the covers. In other words, under the covers they essentially do the following:

import grails.converters.*

...
render book as XML

// or render book as JSON

Why the separation between converters and renderers? Well a renderer has more flexibility to use whatever rendering technology you chose. When implementing a custom renderer you could use Jackson, Google Gson or any Java library to implement the renderer. Converters on the other hand are very much tied to Grails' own marshalling implementation.

Date and Time Rendering

The JSON converter renders date and time values the same way as Spring Boot’s default Jackson JsonMapper, so a Grails application and a Spring Boot application return the same JSON for the same values:

Type Rendered as

java.util.Date, java.sql.Date, java.sql.Timestamp, Calendar, XMLGregorianCalendar

a UTC instant with millisecond precision, such as "2025-10-08T07:48:46.407Z"

java.sql.Time

its wall-clock time in the JVM default time zone, such as "01:48:46"

Instant

ISO-8601, such as "2025-10-08T07:48:46.407254Z"

LocalDate, LocalTime, LocalDateTime

ISO-8601 without a time zone, such as "2025-10-08", "01:48:46.407254", "2025-10-08T01:48:46.407254"

OffsetDateTime, ZonedDateTime, OffsetTime

ISO-8601 with the offset and without a zone ID, such as "2025-10-08T01:48:46.407254-06:00", "03:00:00-03:00"

YearMonth, MonthDay, Duration, Period, javax.xml.datatype.Duration

ISO-8601, such as "2026-09", "--09-25", "PT1H30M", "P1Y2M3D", "P1DT2H"

Year

a number, such as 2026

ZoneId, ZoneOffset, TimeZone

the ID, such as "America/Sao_Paulo" or "-03:00"

An OffsetTime keeps its seconds and writes a fraction of a second with only the digits it needs, as JSON views render it, where Spring Boot writes "03:00-03:00". A map keyed by Date, Calendar or ZonedDateTime values renders its keys as those values render by default, such as "2025-10-08T07:48:46.407Z", also where grails.converters.json.date or a registered marshaller changes how the values render; other map keys render with their toString(). JSON views render dates and times the same way.

A Month renders by its name, such as "SEPTEMBER", as every enum does, where Spring Boot writes its number. Data binding binds a Month from either.

Setting grails.converters.json.date to javascript renders java.util.Date values as JavaScript new Date(…​) constructors instead. To render a type differently, register an object marshaller for it, for example in BootStrap.init. This one renders a Month as its number, as Spring Boot does:

JSON.registerObjectMarshaller(Month) { Month month ->
    month.value
}

10.10.2 Implementing a Custom Renderer

If you want even more control of the rendering or prefer to use your own marshalling techniques then you can implement your own Renderer instance. For example below is a simple implementation that customizes the rendering of the Book class:

package myapp
import grails.rest.render.*
import grails.web.mime.MimeType

class BookXmlRenderer extends AbstractRenderer<Book> {
    BookXmlRenderer() {
        super(Book, [MimeType.XML,MimeType.TEXT_XML] as MimeType[])
    }

    void render(Book object, RenderContext context) {
        context.contentType = MimeType.XML.name

        def xml = new groovy.xml.MarkupBuilder(context.writer)
        xml.book(id: object.id, title:object.title)
    }
}

The AbstractRenderer super class has a constructor that takes the class that it renders and the MimeType(s) that are accepted (via the ACCEPT header or file extension) for the renderer.

To configure this renderer, simply add it is a bean to grails-app/conf/spring/resources.groovy:

beans = {
    bookRenderer(myapp.BookXmlRenderer)
}

The result will be that all Book instances will be rendered in the following format:

<book id="1" title="The Stand"/>
If you change the rendering to a completely different format like the above, then you also need to change the binding if you plan to support POST and PUT requests. Grails will not automatically know how to bind data from a custom XML format to a domain class otherwise. See the section on "Customizing Binding of Resources" for further information.

Container Renderers

A grails.rest.render.ContainerRenderer is a renderer that renders responses for containers of objects (lists, maps, collections etc.). The interface is largely the same as the Renderer interface except for the addition of the getComponentType() method, which should return the "contained" type. For example:

class BookListRenderer implements ContainerRenderer<List, Book> {
    Class<List> getTargetType() { List }
    Class<Book> getComponentType() { Book }
    MimeType[] getMimeTypes() { [ MimeType.XML] as MimeType[] }
    void render(List object, RenderContext context) {
        ....
    }
}

10.10.3 Using GSP to Customize Rendering

You can also customize rendering on a per action basis using Groovy Server Pages (GSP). For example given the show action mentioned previously:

def show(Book book) {
    respond book
}

You could supply a show.xml.gsp file to customize the rendering of the XML:

<%@page contentType="application/xml"%>
<book id="${book.id}" title="${book.title}"/>

10.11 Hypermedia as the Engine of Application State

HATEOAS, an abbreviation for Hypermedia as the Engine of Application State, is a common pattern applied to REST architectures that uses hypermedia and linking to define the REST API.

Hypermedia (also called Mime or Media Types) are used to describe the state of a REST resource, and links tell clients how to transition to the next state. The format of the response is typically JSON or XML, although standard formats such as Atom and/or HAL are frequently used.

10.11.1 HAL Support

HAL is a standard exchange format commonly used when developing REST APIs that follow HATEOAS principals. An example HAL document representing a list of orders can be seen below:

{
    "_links": {
        "self": { "href": "/orders" },
        "next": { "href": "/orders?page=2" },
        "find": {
            "href": "/orders{?id}",
            "templated": true
        },
        "admin": [{
            "href": "/admins/2",
            "title": "Fred"
        }, {
            "href": "/admins/5",
            "title": "Kate"
        }]
    },
    "currentlyProcessing": 14,
    "shippedToday": 20,
    "_embedded": {
        "order": [{
            "_links": {
                "self": { "href": "/orders/123" },
                "basket": { "href": "/baskets/98712" },
                "customer": { "href": "/customers/7809" }
            },
            "total": 30.00,
            "currency": "USD",
            "status": "shipped"
        }, {
            "_links": {
                "self": { "href": "/orders/124" },
                "basket": { "href": "/baskets/97213" },
                "customer": { "href": "/customers/12369" }
            },
            "total": 20.00,
            "currency": "USD",
            "status": "processing"
        }]
    }
}

Exposing Resources Using HAL

To return HAL instead of regular JSON for a resource you can simply override the renderer in grails-app/conf/spring/resources.groovy with an instance of grails.rest.render.hal.HalJsonRenderer (or HalXmlRenderer for the XML variation):

import grails.rest.render.hal.*
beans = {
    halBookRenderer(HalJsonRenderer, rest.test.Book)
}

You will also need to update the acceptable response formats for the resource so that the HAL format is included. Not doing so will result in a 406 - Not Acceptable response being returned from the server.

This can be done by setting the formats attribute of the Resource transformation:

import grails.rest.*

@Resource(uri='/books', formats=['json', 'xml', 'hal'])
class Book {
    ...
}

Or by updating the responseFormats in the controller:

class BookController extends RestfulController<Book> {
    static responseFormats = ['json', 'xml', 'hal']

    // ...
}

With the bean in place requesting the HAL content type will return HAL:

$ curl -i -H "Accept: application/hal+json" http://localhost:8080/books/1

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=ISO-8859-1

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/books/1",
      "hreflang": "en",
      "type": "application/hal+json"
    }
  },
  "title": "\"The Stand\""
}

To use HAL XML format simply change the renderer:

import grails.rest.render.hal.*
beans = {
    halBookRenderer(HalXmlRenderer, rest.test.Book)
}

Rendering Collections Using HAL

To return HAL instead of regular JSON for a list of resources you can simply override the renderer in grails-app/conf/spring/resources.groovy with an instance of grails.rest.render.hal.HalJsonCollectionRenderer:

import grails.rest.render.hal.*
beans = {
    halBookCollectionRenderer(HalJsonCollectionRenderer, rest.test.Book)
}

With the bean in place requesting the HAL content type will return HAL:

$ curl -i -H "Accept: application/hal+json" http://localhost:8080/books
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 17 Oct 2013 02:34:14 GMT

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/books",
      "hreflang": "en",
      "type": "application/hal+json"
    }
  },
  "_embedded": {
    "book": [
      {
        "_links": {
          "self": {
            "href": "http://localhost:8080/books/1",
            "hreflang": "en",
            "type": "application/hal+json"
          }
        },
        "title": "The Stand"
      },
      {
        "_links": {
          "self": {
            "href": "http://localhost:8080/books/2",
            "hreflang": "en",
            "type": "application/hal+json"
          }
        },
        "title": "Infinite Jest"
      },
      {
        "_links": {
          "self": {
            "href": "http://localhost:8080/books/3",
            "hreflang": "en",
            "type": "application/hal+json"
          }
        },
        "title": "Walden"
      }
    ]
  }
}

Notice that the key associated with the list of Book objects in the rendered JSON is book which is derived from the type of objects in the collection, namely Book. In order to customize the value of this key assign a value to the collectionName property on the HalJsonCollectionRenderer bean as shown below:

import grails.rest.render.hal.*
beans = {
    halBookCollectionRenderer(HalCollectionJsonRenderer, rest.test.Book) {
        collectionName = 'publications'
    }
}

With that in place the rendered HAL will look like the following:

$ curl -i -H "Accept: application/hal+json" http://localhost:8080/books
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 17 Oct 2013 02:34:14 GMT

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/books",
      "hreflang": "en",
      "type": "application/hal+json"
    }
  },
  "_embedded": {
    "publications": [
      {
        "_links": {
          "self": {
            "href": "http://localhost:8080/books/1",
            "hreflang": "en",
            "type": "application/hal+json"
          }
        },
        "title": "The Stand"
      },
      {
        "_links": {
          "self": {
            "href": "http://localhost:8080/books/2",
            "hreflang": "en",
            "type": "application/hal+json"
          }
        },
        "title": "Infinite Jest"
      },
      {
        "_links": {
          "self": {
            "href": "http://localhost:8080/books/3",
            "hreflang": "en",
            "type": "application/hal+json"
          }
        },
        "title": "Walden"
      }
    ]
  }
}

Using Custom Media / Mime Types

If you wish to use a custom Mime Type then you first need to declare the Mime Types in grails-app/conf/application.groovy:

grails.mime.types = [
    all:      "*/*",
    book:     "application/vnd.books.org.book+json",
    bookList: "application/vnd.books.org.booklist+json",
    ...
]

Or the equivalent in grails-app/conf/application.yml:

grails:
    mime:
        types:
            all: "*/*"
            book: "application/vnd.books.org.book+json"
            bookList: "application/vnd.books.org.booklist+json"
            ...:
It is critical that place your new mime types after the 'all' Mime Type because if the Content Type of the request cannot be established then the first entry in the map is used for the response. If you have your new Mime Type at the top then Grails will always try and send back your new Mime Type if the requested Mime Type cannot be established.

Then override the renderer to return HAL using the custom Mime Types:

import grails.rest.render.hal.*
import grails.web.mime.*

beans = {
    halBookRenderer(HalJsonRenderer, rest.test.Book, new MimeType("application/vnd.books.org.book+json", [v:"1.0"]))
    halBookListRenderer(HalJsonCollectionRenderer, rest.test.Book, new MimeType("application/vnd.books.org.booklist+json", [v:"1.0"]))
}

In the above example the first bean defines a HAL renderer for a single book instance that returns a Mime Type of application/vnd.books.org.book+json. The second bean defines the Mime Type used to render a collection of books (in this case application/vnd.books.org.booklist+json).

application/vnd.books.org.booklist+json is an example of a media-range (https://www.w3.org/Protocols/rfc2616/rfc2616.html - Header Field Definitions). This example uses entity (book) and operation (list) to form the media-range values but in reality, it may not be necessary to create a separate Mime type for each operation. Further, it may not be necessary to create Mime types at the entity level. See the section on "Versioning REST resources" for further information about how to define your own Mime types.

With this in place issuing a request for the new Mime Type returns the necessary HAL:

$ curl -i -H "Accept: application/vnd.books.org.book+json" http://localhost:8080/books/1

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/vnd.books.org.book+json;charset=ISO-8859-1

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/books/1",
      "hreflang": "en",
      "type": "application/vnd.books.org.book+json"
    }
  },
  "title": "\"The Stand\""
}

An important aspect of HATEOAS is the usage of links that describe the transitions the client can use to interact with the REST API. By default the HalJsonRenderer will automatically create links for you for associations and to the resource itself (using the "self" relationship).

However you can customize link rendering using the link method that is added to all domain classes annotated with grails.rest.Resource or any class annotated with grails.rest.Linkable. For example, the show action can be modified as follows to provide a new link in the resulting output:

def show(Book book) {
    book.link rel:'publisher', href: g.createLink(absolute: true, resource:"publisher", params:[bookId: book.id])
    respond book
}

Which will result in output such as:

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/books/1",
      "hreflang": "en",
      "type": "application/vnd.books.org.book+json"
    }
    "publisher": {
        "href": "http://localhost:8080/books/1/publisher",
        "hreflang": "en"
    }
  },
  "title": "\"The Stand\""
}

The link method can be passed named arguments that match the properties of the grails.rest.Link class.

10.11.2 Atom Support

Atom is another standard interchange format used to implement REST APIs. An example of Atom output can be seen below:

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="https://www.w3.org/2005/Atom">

 <title>Example Feed</title>
 <link href="https://example.org/"/>
 <updated>2003-12-13T18:30:02Z</updated>
 <author>
   <name>John Doe</name>
 </author>
 <id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>

 <entry>
   <title>Atom-Powered Robots Run Amok</title>
   <link href="https://example.org/2003/12/13/atom03"/>
   <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
   <updated>2003-12-13T18:30:02Z</updated>
   <summary>Some text.</summary>
 </entry>

</feed>

To use Atom rendering again simply define a custom renderer:

import grails.rest.render.atom.*
beans = {
    halBookRenderer(AtomRenderer, rest.test.Book)
    halBookListRenderer(AtomCollectionRenderer, rest.test.Book)
}

10.11.3 Vnd.Error Support

Vnd.Error is a standardised way of expressing an error response.

By default when a validation error occurs when attempting to POST new resources then the errors object will be sent back allow with a 422 respond code:

$ curl -i -H "Accept: application/json"  -H "Content-Type: application/json" -X POST -d "" http://localhost:8080/books

HTTP/1.1 422 Unprocessable Entity
Server: Apache-Coyote/1.1
Content-Type: application/json;charset=ISO-8859-1

{
  "errors": [
    {
      "object": "rest.test.Book",
      "field": "title",
      "rejected-value": null,
      "message": "Property [title] of class [class rest.test.Book] cannot be null"
    }
  ]
}

If you wish to change the format to Vnd.Error then simply register grails.rest.render.errors.VndErrorJsonRenderer bean in grails-app/conf/spring/resources.groovy:

beans = {
    vndJsonErrorRenderer(grails.rest.render.errors.VndErrorJsonRenderer)
    // for Vnd.Error XML format
    vndXmlErrorRenderer(grails.rest.render.errors.VndErrorXmlRenderer)
}

Then if you alter the client request to accept Vnd.Error you get an appropriate response:

$ curl -i -H "Accept: application/vnd.error+json,application/json" -H "Content-Type: application/json" -X POST -d "" http://localhost:8080/books
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: application/vnd.error+json;charset=ISO-8859-1

[
    {
        "logref": "book.nullable,
        "message": "Property [title] of class [class rest.test.Book] cannot be null",
        "_links": {
            "resource": {
                "href": "http://localhost:8080/rest-test/books"
            }
        }
    }
]

10.12 Customizing Binding of Resources

The framework provides a sophisticated but simple mechanism for binding REST requests to domain objects and command objects. One way to take advantage of this is to bind the request property in a controller the properties of a domain class. Given the following XML as the body of the request, the createBook action will create a new Book and assign "The Stand" to the title property and "Stephen King" to the authorName property.

<?xml version="1.0" encoding="UTF-8"?>
<book>
    <title>The Stand</title>
    <authorName>Stephen King</authorName>
</book>
class BookController {

    def createBook() {
        def book = new Book()
        book.properties = request

        // ...
    }
}

Command objects will automatically be bound with the body of the request:

class BookController {
    def createBook(BookCommand book) {

        // ...
    }
}

class BookCommand {
    String title
    String authorName
}

If the command object type is a domain class and the root element of the XML document contains an id attribute, the id value will be used to retrieve the corresponding persistent instance from the database and then the rest of the document will be bound to the instance. If no corresponding record is found in the database, the command object reference will be null.

<?xml version="1.0" encoding="UTF-8"?>
<book id="42">
    <title>Walden</title>
    <authorName>Henry David Thoreau</authorName>
</book>
class BookController {
    def updateBook(Book book) {
        // The book will have been retrieved from the database and updated
        // by doing something like this:
        //
        // book == Book.get('42')
        // if(book != null) {
        //    book.properties = request
        // }
        //
        // the code above represents what the framework will
        // have done. There is no need to write that code.

        // ...

    }
}

The data binding depends on an instance of the DataBindingSource interface created by an instance of the DataBindingSourceCreator interface. The specific implementation of DataBindingSourceCreator will be selected based on the contentType of the request. Several implementations are provided to handle common content types. The default implementations will be fine for most use cases. The following table lists the content types which are supported by the core framework and which DataBindingSourceCreator implementations are used for each. All of the implementation classes are in the org.grails.databinding.bindingsource package.

Content Type(s) Bean Name DataBindingSourceCreator Impl.

application/xml, text/xml

xmlDataBindingSourceCreator

XmlDataBindingSourceCreator

application/json, text/json

jsonDataBindingSourceCreator

JsonDataBindingSourceCreator

application/hal+json

halJsonDataBindingSourceCreator

HalJsonDataBindingSourceCreator

application/hal+xml

halXmlDataBindingSourceCreator

HalXmlDataBindingSourceCreator

In order to provide your own DataBindingSourceCreator for any of those content types, write a class which implements DataBindingSourceCreator and register an instance of that class in the Spring application context. If you are replacing one of the existing helpers, use the corresponding bean name from above. If you are providing a helper for a content type other than those accounted for by the core framework, the bean name may be anything that you like but you should take care not to conflict with one of the bean names above.

The DataBindingSourceCreator interface defines just 2 methods:

package org.grails.databinding.bindingsource

import grails.web.mime.MimeType
import grails.databinding.DataBindingSource

/**
 * A factory for DataBindingSource instances
 *
 * @since 2.3
 * @see DataBindingSourceRegistry
 * @see DataBindingSource
 *
 */
interface DataBindingSourceCreator {

    /**
     * `return All of the {`link MimeType} supported by this helper
     */
    MimeType[] getMimeTypes()

    /**
     * Creates a DataBindingSource suitable for binding bindingSource to bindingTarget
     *
     * @param mimeType a mime type
     * @param bindingTarget the target of the data binding
     * @param bindingSource the value being bound
     * @return a DataBindingSource
     */
    DataBindingSource createDataBindingSource(MimeType mimeType, Object bindingTarget, Object bindingSource)
}

AbstractRequestBodyDataBindingSourceCreator is an abstract class designed to be extended to simplify writing custom DataBindingSourceCreator classes. Classes which extend AbstractRequestbodyDatabindingSourceCreator need to implement a method named createBindingSource which accepts an InputStream as an argument and returns a DataBindingSource as well as implementing the getMimeTypes method described in the DataBindingSourceCreator interface above. The InputStream argument to createBindingSource provides access to the body of the request.

The code below shows a simple implementation.

src/main/groovy/com/demo/myapp/databinding/MyCustomDataBindingSourceCreator.groovy
package com.demo.myapp.databinding

import grails.web.mime.MimeType
import grails.databinding.DataBindingSource
import org...databinding.SimpleMapDataBindingSource
import org...databinding.bindingsource.AbstractRequestBodyDataBindingSourceCreator

/**
 * A custom DataBindingSourceCreator capable of parsing key value pairs out of
 * a request body containing a comma separated list of key:value pairs like:
 *
 * name:Herman,age:99,town:STL
 *
 */
class MyCustomDataBindingSourceCreator extends AbstractRequestBodyDataBindingSourceCreator {

    @Override
    public MimeType[] getMimeTypes() {
        [new MimeType('text/custom+demo+csv')] as MimeType[]
    }

    @Override
    protected DataBindingSource createBindingSource(InputStream inputStream) {
        def map = [:]

        def reader = new InputStreamReader(inputStream)

        // this is an obviously naive parser and is intended
        // for demonstration purposes only.

        reader.eachLine { line ->
            def keyValuePairs = line.split(',')
            keyValuePairs.each { keyValuePair ->
                if(keyValuePair?.trim()) {
                    def keyValuePieces = keyValuePair.split(':')
                    def key = keyValuePieces[0].trim()
                    def value = keyValuePieces[1].trim()
                    map<<key>> = value
                }
            }
        }

        // create and return a DataBindingSource which contains the parsed data
        new SimpleMapDataBindingSource(map)
    }
}

An instance of MyCustomDataSourceCreator needs to be registered in the spring application context.

grails-app/conf/spring/resources.groovy
beans = {

    myCustomCreator com.demo.myapp.databinding.MyCustomDataBindingSourceCreator

    // ...
}

With that in place the framework will use the myCustomCreator bean any time a DataBindingSourceCreator is needed to deal with a request which has a contentType of "text/custom+demo+csv".

10.13 RSS and Atom

No direct support is provided for RSS or Atom within Grails. You could construct RSS or ATOM feeds with the render method’s XML capability.