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
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
java
package com.acme.billing.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties;
Feature-flagged beans with Spring @ConditionalOnProperty
feature-flags
conditional-beans
strategy-pattern
Intermediate
5 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
java
public static Map<String, String> parseCookieHeader(String header) { Map<String, String> cookies = new LinkedHashMap<>(); if (header == null || header.isBlank()) { return cookies;
Parsing an HTTP Cookie header in Java
string-parsing
http
url-decoding
Intermediate
6 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.