java
37 lines · 6 steps
Request-scoped rate caching in Spring
A request-scoped bean deduplicates exchange-rate lookups so identical calls within one HTTP request only hit the client once.
Explained by
highlit
1@Service
2@RequiredArgsConstructor
3public class ExchangeRateService {
4
5 private final ExchangeRateClient client;
6 private final RequestScopedRateCache cache;
7
8 public BigDecimal rateFor(String from, String to) {
9 String key = from + "->" + to;
10 return cache.computeIfAbsent(key, () -> client.fetchRate(from, to));
11 }
12}
13
14@Component
15@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
16class RequestScopedRateCache {
17
18 private final ConcurrentMap<String, CompletableFuture<BigDecimal>> inflight = new ConcurrentHashMap<>();
19
20 BigDecimal computeIfAbsent(String key, Supplier<BigDecimal> loader) {
21 CompletableFuture<BigDecimal> future = inflight.computeIfAbsent(key, k -> {
22 CompletableFuture<BigDecimal> f = new CompletableFuture<>();
23 try {
24 f.complete(loader.get());
25 } catch (RuntimeException ex) {
26 f.completeExceptionally(ex);
27 }
28 return f;
29 });
30 try {
31 return future.join();
32 } catch (CompletionException ex) {
33 inflight.remove(key, future);
34 throw (RuntimeException) ex.getCause();
35 }
36 }
37}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A request-scoped bean gives you a per-request cache lifetime for free, no manual cleanup between requests.
- 2Storing futures rather than values in a ConcurrentMap collapses concurrent identical calls into a single computation.
- 3Removing a failed entry keeps failures from being cached, so the next call retries cleanly.
Related explainers
java
@Configuration public class WebConfig { @Bean
HTTP caching with ETags in Spring
http-caching
etag
servlet-filters
Intermediate
6 steps
java
package com.acme.catalog.persistence; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners;
Auditing timestamps with a JPA MappedSuperclass in Spring
jpa
auditing
lifecycle-callbacks
Intermediate
5 steps
ruby
require "thread" class ConnectionPool class TimeoutError < StandardError; end
Building a thread-safe connection pool in Ruby
concurrency
resource-pooling
mutex
Advanced
8 steps
go
package dedup import ( "hash/fnv"
A rolling Bloom filter deduper in Go
bloom-filter
deduplication
concurrency
Advanced
7 steps
javascript
import { useDeferredValue, useMemo, useState } from "react"; function ProductSearch({ products }) { const [query, setQuery] = useState("");
Keeping search input snappy with useDeferredValue in React
concurrent-rendering
deferred-value
memoization
Intermediate
7 steps
go
package server import ( "net/http"
Rate limiting HTTP handlers with a token bucket
rate-limiting
token-bucket
middleware
Advanced
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/request-scoped-rate-caching-in-spring-explained-java-52ad/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.