Google Sheets as a REST API in Java (Spring Boot)
Consume SheetsAPI from a Spring Boot application using RestClient, typed records, Spring Cache, and reactive WebClient for non-blocking calls.
3 min read
Spring Boot's RestClient (Spring 6.1+) and typed Java records make it straightforward
to integrate SheetsAPI into a Java backend. This guide covers synchronous fetching, search,
pagination, authentication, and Spring Cache integration.
Prerequisites
- Java 21+ and Spring Boot 3.2+
- A Google Sheet with a header row
- A SheetsAPI account and a
YOUR_USER_KEYkey from the dashboard
1. Response types as Java records
// src/main/java/com/example/sheets/SheetResponse.java
package com.example.sheets;
import java.util.List;
public record SheetResponse<T>(List<T> data, SheetMeta meta) {
public record SheetMeta(int total, int limit, int offset) {}
}// src/main/java/com/example/sheets/Product.java
package com.example.sheets;
public record Product(String name, String price, String category, String stock) {}2. SheetsAPI service bean
// src/main/java/com/example/sheets/SheetsApiService.java
package com.example.sheets;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
import org.springframework.web.util.UriComponentsBuilder;
@Service
public class SheetsApiService {
private static final String BASE = "https://sheetsapi.gkit.mreshank.com/api/spreadsheets";
private final RestClient restClient;
private final String userKey;
public SheetsApiService(
@Value("${sheetsapi.user-key}") String userKey,
@Value("${sheetsapi.api-key:}") String apiKey) {
this.userKey = userKey;
this.restClient = RestClient.builder()
.defaultHeader("Authorization", "Bearer " + apiKey)
.build();
}
public <T> SheetResponse<T> query(
String sheet,
ParameterizedTypeReference<SheetResponse<T>> type,
QueryOptions opts) {
var uri = UriComponentsBuilder
.fromUriString(BASE + "/{key}/{sheet}")
.queryParamIfPresent("limit", java.util.Optional.ofNullable(opts.limit()))
.queryParamIfPresent("offset", java.util.Optional.ofNullable(opts.offset()))
.queryParamIfPresent("search", java.util.Optional.ofNullable(opts.search()))
.queryParamIfPresent("sort", java.util.Optional.ofNullable(opts.sort()))
.queryParamIfPresent("fields", java.util.Optional.ofNullable(opts.fields()))
.buildAndExpand(userKey, sheet)
.toUri();
return restClient.get()
.uri(uri)
.retrieve()
.body(type);
}
public record QueryOptions(
Integer limit,
Integer offset,
String search,
String sort,
String fields
) {
public static QueryOptions defaults() {
return new QueryOptions(20, 0, null, null, null);
}
}
}3. Application properties
# src/main/resources/application.properties
sheetsapi.user-key=YOUR_USER_KEY
sheetsapi.api-key=sk_your_api_key_here4. Controller
// src/main/java/com/example/sheets/ProductsController.java
package com.example.sheets;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/products")
public class ProductsController {
private final SheetsApiService sheetsApi;
public ProductsController(SheetsApiService sheetsApi) {
this.sheetsApi = sheetsApi;
}
@GetMapping
public SheetResponse<Product> list(
@RequestParam(defaultValue = "20") int limit,
@RequestParam(defaultValue = "0") int offset,
@RequestParam(required = false) String q) {
String search = (q != null && !q.isBlank()) ? "name:" + q : null;
return sheetsApi.query(
"Products",
new ParameterizedTypeReference<>() {},
new SheetsApiService.QueryOptions(limit, offset, search, "name", null)
);
}
}5. Spring Cache integration
Add the spring-boot-starter-cache dependency and enable caching:
// src/main/java/com/example/Application.java
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}Cache the query result:
@Cacheable(value = "products", key = "#search ?: 'all'")
public List<Product> cachedProducts(String search) {
var result = sheetsApi.query(
"Products",
new ParameterizedTypeReference<SheetResponse<Product>>() {},
new SheetsApiService.QueryOptions(200, 0, search, null, null)
);
return result.data();
}Configure a simple in-memory cache with TTL using Caffeine:
<!-- pom.xml -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>spring.cache.type=caffeine
spring.cache.caffeine.spec=maximumSize=500,expireAfterWrite=5m6. Reactive WebClient (non-blocking)
For reactive Spring WebFlux applications:
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@Service
public class ReactiveSheetsService {
private final WebClient webClient;
private final String userKey;
public ReactiveSheetsService(
@Value("${sheetsapi.user-key}") String userKey,
@Value("${sheetsapi.api-key}") String apiKey) {
this.userKey = userKey;
this.webClient = WebClient.builder()
.baseUrl("https://sheetsapi.gkit.mreshank.com")
.defaultHeader("Authorization", "Bearer " + apiKey)
.build();
}
public <T> Mono<SheetResponse<T>> query(
String sheet,
Class<T> itemType,
int limit) {
return webClient.get()
.uri("/api/spreadsheets/{key}/{sheet}?limit={limit}", userKey, sheet, limit)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<SheetResponse<T>>() {});
}
}7. Pagination utility
public <T> List<T> fetchAll(String sheet, ParameterizedTypeReference<SheetResponse<T>> type) {
List<T> all = new java.util.ArrayList<>();
int offset = 0;
final int pageSize = 100;
while (true) {
var page = sheetsApi.query(sheet, type,
new SheetsApiService.QueryOptions(pageSize, offset, null, null, null));
all.addAll(page.data());
offset += pageSize;
if (offset >= page.meta().total()) break;
}
return all;
}Query parameter reference
| Parameter | Example | Description |
|---|---|---|
limit | 50 | Rows per page (default: 20, max: 500) |
offset | 0 | Row offset for pagination |
search | category:books | Filter by field:value |
sort | price or -price | Ascending / descending |
fields | name,price | Return only named columns |
Summary
| Concern | Approach |
|---|---|
| HTTP | Spring RestClient (sync) or WebClient (reactive) |
| Types | Java records + ParameterizedTypeReference |
| Auth | Authorization: Bearer header |
| Caching | @Cacheable + Caffeine TTL |
| Pagination | Offset loop until offset >= meta.total() |
SheetsAPI returns standard JSON - Spring Boot's mature HTTP clients and caching abstractions handle the rest with minimal boilerplate.