Building a Grails REST Client with Spring RestTemplate
Modern enterprise systems rarely stand alone. From a fintech platform in Sydney pulling market data from an aggregator to a logistics tool in Melbourne coordinating with Australia Post, the applications developers build need to converse with other services over HTTP. Grails, sitting on top of Spring Boot, makes this kind of integration especially approachable because the entire Spring ecosystem is only a few lines away. When a controller, service or scheduled job needs to call out to another system, Spring's RestTemplate is one of the most familiar and dependable options available.
RestTemplate has been around since the early days of Spring and, while newer reactive alternatives exist, it remains the workhorse for synchronous HTTP communication in JVM applications. It abstracts away the boilerplate of opening connections, marshalling payloads and reading responses, leaving developers to focus on the actual business exchange. For teams maintaining legacy APIs or building straightforward batch jobs that synchronously exchange data with partners, the predictability of RestTemplate is a feature, not a limitation.
In a Grails application, wiring RestTemplate into the framework is a matter of dependency injection. Once a bean is registered, services can inject it wherever HTTP calls are required, and Grails' auto-configuration handles most of the underlying plumbing. This article walks through configuring RestTemplate, exercising the main HTTP verbs, dealing with errors and writing reliable tests for the resulting client. The examples assume a standard Grails 5 project generated from the official guides, running locally during the Australian afternoon when most teams wrap up their stand-ups and start focused coding blocks.
For readers who prefer a guided video walkthrough alongside written material, the course outline lays out the full learning path from beginner Groovy concepts through advanced deployment topics.
Configuring the RestTemplate bean in Grails
The cleanest way to expose RestTemplate to the rest of a Grails application is through a Spring bean declared in grails-app/conf/spring/resources.groovy or, for newer projects, in a dedicated configuration class. A typical definition includes a sensible connection timeout, a read timeout and a sensible default for content negotiation. Most teams start with something like a RestTemplateBuilder that supplies sane defaults and allows customisation:
@Bean
RestTemplate restTemplate(RestTemplateBuilder builder) {
builder
.connectTimeout(Duration.ofMillis(500))
.readTimeout(Duration.ofSeconds(2))
.build()
}
Once declared, the bean can be injected into any service:
class BookingService {
RestTemplate restTemplate
List fetchOpenSlots(String venueId) {
// ...
}
}
For local development on a laptop in Brisbane or Perth, the default timeouts of essentially "wait forever" are rarely a problem because the target server is usually on the same machine. In production, particularly when calling external APIs from an AWS Sydney region into overseas endpoints, those defaults become dangerous. A flaky upstream can leave threads hanging and, before long, exhaust a connection pool.
Practical timeout values depend on the use case. For a payment authorisation call that needs to return within a second to satisfy a checkout flow, setting connectTimeout to 500ms and readTimeout to 800ms is reasonable. For a nightly reconciliation job that pulls millions of records, generous values such as 10 seconds for connect and 60 seconds for read make more sense. Whichever values are chosen, they should be explicitly set rather than left to inherited defaults.
Performing GET requests and parsing JSON
GET is by far the most common verb a REST client performs, and RestTemplate handles it with a single method call. The classic pattern is:
def response = restTemplate.getForEntity(url, Map.class)
if (response.statusCode.is2xxSuccessful()) {
Map body = response.body
// process the payload
}
In Grails, returning Map.class works because Jackson is already on the classpath. For strongly typed responses, defining a Groovy or Java domain-like class and passing its Class object gives compile-time safety:
class Shipment {
String trackingId
String status
Date estimatedDelivery
}
Shipment shipment = restTemplate.getForObject(url, Shipment.class)
This works well for many Australian e-commerce integrations, where a courier API returns a small, predictable payload. When the response includes nested collections or optional fields, the Groovy class should use List<T>, BigDecimal for monetary amounts (avoiding floating-point surprises when handling AUD) and wrapper types like Long for identifiers that might legitimately be missing.
One subtle pitfall with RestTemplate is its default conversion of HTTP errors into exceptions thrown by the same call. A 404 from the upstream service will not return a ResponseEntity with status 404; it will throw an HttpClientErrorException. This behaviour is fine for fail-fast services but problematic when the calling code wants to inspect the error body and react accordingly. The solution is to plug in a custom ResponseErrorHandler, which is covered in the next section.
Sending POST, PUT and DELETE with request bodies
When the REST client needs to create or update resources, the exchange pattern in RestTemplate gives the most control. The method accepts an HttpEntity, which bundles the headers and body, and returns a ResponseEntity that contains both the status code and any response body:
HttpHeaders headers = new HttpHeaders()
headers.contentType = MediaType.APPLICATION_JSON
headers.setBearerAuth(authToken)
def payload = [customerId: 'AU-47291', amount: 149.95]
HttpEntity request = new HttpEntity<>(payload, headers)
ResponseEntity response = restTemplate.exchange(
url,
HttpMethod.POST,
request,
Map.class
)
The same approach works for PUT and PATCH, with HttpMethod swapped out. For DELETE, the body is usually empty but the headers often carry an idempotency key, particularly when integrating with Australian banking APIs that follow the Consumer Data Right standards. In those contexts, auditability matters, and including a correlation identifier in every request header is a habit worth forming early.
A common pattern in Grails services is to wrap this exchange call in a private helper that converts the response into a domain object or throws a domain-specific exception. Something like:
private <T> T postForResult(String url, Object body, Class<T> responseType) {
HttpEntity entity = new HttpEntity<>(body, jsonHeaders())
try {
ResponseEntity response = restTemplate.exchange(
url, HttpMethod.POST, entity, responseType
)
return response.body
} catch (HttpClientErrorException e) {
throw new UpstreamApiException(e.statusCode, e.responseBodyAsString)
}
}
This keeps controllers clean and concentrates the awkward translation from HTTP world to business world in one place.
Managing errors, timeouts and interceptors
Reliable REST clients handle three classes of failure: connection-level problems, application-level errors returned as 4xx and 5xx, and unexpected runtime exceptions. RestTemplate's default behaviour maps all three to thrown exceptions, which is fine for command-line tools but often undesirable in long-running web applications where the upstream call should fail gracefully.
A custom ResponseErrorHandler gives full control:
class SoftErrorHandler extends DefaultResponseErrorHandler {
@Override
boolean hasError(ClientHttpResponse response) {
// treat all 5xx as errors but swallow 4xx so the caller can inspect them
response.statusCode.series() == HttpStatus.Series.SERVER_ERROR
}
}
Once defined, attach it via a RestTemplate customiser or simply inject it into the bean configuration:
@Bean
RestTemplate restTemplate(RestTemplateBuilder builder) {
builder
.errorHandler(new SoftErrorHandler())
.connectTimeout(Duration.ofMillis(500))
.readTimeout(Duration.ofSeconds(2))
.interceptors(new UserAgentInterceptor('GrailsClient/1.0'))
.build()
}
Interceptors are particularly useful for cross-cutting concerns. A logging interceptor adds request and response breadcrumbs that prove invaluable when chasing down a bug at 2am during a scheduled run. An authentication interceptor attaches an OAuth token and refreshes it before expiry. A retry interceptor can be configured to resend idempotent requests when the response is a transient 503 or when the underlying connection drops unexpectedly.
For teams deploying to managed cloud platforms, packaging and shipping the application is the last hurdle. The AppFog deployment guide walks through the deployment workflow, including how environment-specific timeouts and interceptor beans should be wired so production traffic behaves predictably regardless of which availability zone the application runs in.
Testing the REST client with Spock
A REST client that ships without tests is a liability. Spock, bundled with every Grails project, makes both unit and integration testing straightforward.
For a unit test, MockRestServiceServer attaches to a RestTemplate and replays canned responses:
class BookingServiceSpec extends Specification {
BookingService service
MockRestServiceServer server
def setup() {
RestTemplate template = new RestTemplate()
server = MockRestServiceServer.createServer(template)
service = new BookingService(restTemplate: template)
}
def "fetches open slots for a venue"() {
given:
server.expect(once(), requestTo('/api/venues/v-42/slots'))
.andRespond(withSuccess('{"slots":["10:00","11:00"]}',
MediaType.APPLICATION_JSON))
when:
List slots = service.fetchOpenSlots('v-42')
then:
slots == ['10:00', '11:00']
}
}
This style verifies that the client issues the expected request and correctly transforms the response, without ever touching the network.
For tests that genuinely need to round-trip to a running service, Grails' integration test mode is the right tool. Spinning up WireMock inside a Spock feature method gives a programmable HTTP server that can simulate slow responses, flaky connections and unusual status codes. Combining WireMock for the network layer with the production RestTemplate catches misconfigurations that mocked tests would miss, such as a wrong default Content-Type or a missing header.
A solid rule of thumb is to write the bulk of tests against MockRestServiceServer because they run quickly and deterministically, then keep a small number of WireMock-backed tests as a safety net. Both kinds of tests belong in the same suite, executed by the same Gradle command. Together they give confidence that the REST client behaves the way the code claims it does, on a developer machine in Adelaide as readily as in a CI pipeline running in a Sydney availability zone.
Putting it all together, building a Grails REST client with RestTemplate is more about disciplined configuration than clever code. A timeout here, an interceptor there, a thoughtful error handler and a thorough test suite turn a quick prototype into a component the rest of the application can rely on. The patterns covered here translate directly to the next real integration the project needs — perhaps the warehouse API in Brisbane, the payment gateway in Sydney, or the partner portal in Melbourne — and once they are second nature, every new client the team writes afterwards inherits the same reliability.