
Se você está buscando maneiras de tornar sua API REST mais eficiente e responsiva, então implementar estratégias de cache no backend pode ser um excelente ponto de partida. Neste artigo, vamos mostrar como utilizar os cabeçalhos HTTP Cache-Control
e ETag
em aplicações desenvolvidas com Spring Boot. Além disso, você verá exemplos práticos tanto em Java quanto em Kotlin. E o melhor: todas essas melhorias podem ser aplicadas sem nenhuma alteração no front-end.
Por que usar Cache-Control e ETag?
Ao trabalhar com APIs, é comum lidar com recursos que nem sempre mudam com frequência. No entanto, a cada requisição, esses dados são buscados e enviados novamente ao cliente — mesmo quando não houve nenhuma atualização.
Para resolver esse problema, o protocolo HTTP oferece mecanismos específicos, como Cache-Control
e ETag
. Em conjunto, eles ajudam o cliente a evitar chamadas desnecessárias e, consequentemente, reduzem a carga sobre o servidor.
Cache-Control
define por quanto tempo o cliente pode considerar uma resposta como válida.ETag
permite que o servidor indique se o recurso mudou desde a última vez que foi solicitado.
Com esses dois mecanismos combinados, é possível melhorar a performance da aplicação e otimizar o uso de recursos.
Exemplo prático com Spring Boot
Agora vamos aplicar os conceitos com exemplos práticos. A seguir, você verá como adicionar Cache-Control
e ETag
nas respostas da sua API.
Implementando Cache-Control
Adicionar o cabeçalho Cache-Control
no Spring Boot é simples usando a classe CacheControl
da biblioteca org.springframework.http
.
Java
@GetMapping("/produtos")
public ResponseEntity<List<Produto>> getProdutos() {
List<Produto> produtos = produtoService.buscarTodos();
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
.body(produtos);
}
Kotlin
@GetMapping("/produtos")
fun getProdutos(): ResponseEntity<List<Produto>> {
val produtos = produtoService.buscarTodos()
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
.body(produtos)
}
Neste exemplo, o conteúdo será armazenado em cache por 60 segundos. Após esse tempo, o cliente deve verificar novamente com o servidor.
Adicionando ETag para validar mudanças
O cabeçalho ETag
, em essência, funciona como uma espécie de “impressão digital” da resposta. Assim, sempre que o cliente faz uma nova requisição, ele envia o valor da última ETag
recebida. A partir disso, o servidor pode comparar esse valor com a versão atual do recurso e, caso não haja mudanças, evita reenviar os dados. Por exemplo:
Java
@GetMapping("/produtos")
public ResponseEntity<List<Produto>> getProdutos(
@RequestHeader(value = "If-None-Match", required = false) String ifNoneMatch) {
List<Produto> produtos = produtoService.buscarTodos();
produtos.sort(Comparator.comparing(Produto::getId));
String hashBase = produtos.stream()
.map(p -> p.getId() + ":" + p.getUpdatedAt())
.collect(Collectors.joining("|"));
String eTag = "\"" + Integer.toHexString(hashBase.hashCode()) + "\"";
if (eTag.equals(ifNoneMatch)) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED)
.eTag(eTag)
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
.build();
}
return ResponseEntity.ok()
.eTag(eTag)
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
.body(produtos);
}
Kotlin
@GetMapping("/produtos")
fun getProdutos(
@RequestHeader("If-None-Match", required = false) ifNoneMatch: String?
): ResponseEntity<List<Produto>> {
val produtos = produtoService.buscarTodos().sortedBy { it.id }
val hashBase = produtos.joinToString("|") { "${it.id}:${it.updatedAt}" }
val eTag = "\"" + hashBase.hashCode().toString(16) + "\""
return if (eTag == ifNoneMatch) {
ResponseEntity.status(HttpStatus.NOT_MODIFIED)
.eTag(eTag)
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
.build()
} else {
ResponseEntity.ok()
.eTag(eTag)
.cacheControl(CacheControl.maxAge(60, TimeUnit.SECONDS).cachePublic())
.body(produtos)
}
}
Dica importante sobre listas
Mesmo que o conteúdo da lista seja o mesmo, a ordem dos registros impacta na geração da ETag. Portanto, é fundamental ordenar os dados antes de gerar o hash.
Java
produtos.sort(Comparator.comparing(Produto::getId));
Kotlin
val produtosOrdenados = produtos.sortedBy { it.id }
Quando usar?
- Use
Cache-Control
para dados que mudam com pouca frequência. - Use
ETag
para evitar envio desnecessário de conteúdo. - Use os dois juntos para máxima eficiência de rede e performance.
Além disso, essas técnicas são compatíveis com qualquer front-end moderno, pois fazem uso de padrões HTTP já suportados nativamente.
Conclusão
Utilizar Cache-Control
e ETag
é, sem dúvida, uma forma eficaz de melhorar a performance de APIs REST em projetos Spring Boot. Com apenas alguns ajustes no backend, é possível — como resultado — reduzir o tráfego, economizar recursos e, acima de tudo, oferecer uma experiência mais ágil ao usuário. Além disso, tudo isso pode ser feito sem qualquer alteração no front-end.
Essas práticas são especialmente úteis em endpoints que retornam grandes listas ou recursos que mudam com baixa frequência. Além disso, com os exemplos em Java e Kotlin, ficou fácil aplicar esses conceitos no seu projeto hoje mesmo.
Se você gostou do post, considere compartilhar com sua equipe ou deixar um comentário. Tem dúvidas ou quer ver isso aplicado com paginação, filtros ou outros headers? Me avisa aqui nos comentários!
Fontes e Referências
- Mozilla Developer Network (MDN) – Cache-Control
Explica em detalhes os parâmetros possíveis no cabeçalhoCache-Control
. - Mozilla Developer Network (MDN) – ETag
Página completa com explicações sobre como oETag
funciona no protocolo HTTP. - Spring Framework – CacheControl Documentation
Documentação oficial da classeCacheControl
usada no Spring Boot. - RFC 7232 – HTTP/1.1 Conditional Requests (ETag)
Documento técnico que especifica como funciona oETag
no protocolo HTTP. - Baeldung – Spring ETag Support
Tutorial prático sobre como usarETag
no Spring Boot com código de exemplo.
0 Comentários