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.
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.
GKit is currently undergoing platform maintenance
Be the first to know when all Google Workspace tools are back online, plus get our engineering deep-dives and product announcements directly to your inbox.