Best Exchange Rate API for Java and Spring Boot in 2026
Java still runs the money. Banking backends, e-commerce platforms, ERP systems, and the Spring Boot microservices behind countless SaaS products all process prices, invoices, and payments in multiple currencies. When one of those systems needs exchange rate data, it needs an API that behaves like enterprise infrastructure: stable JSON contracts, standard Bearer authentication, and rates you can trust for financial calculations.
Many currency APIs fall short. Some update once a day, which is a problem the moment your application reprices anything intraday. Some lock historical rates or non-USD base currencies behind expensive plans. Others force you into awkward authentication schemes that fight Spring's conventions.
This article compares the 5 most popular exchange rate APIs for Java and Spring Boot developers in 2026, with working code using Java 11+'s built-in java.net.http.HttpClient. AllRatesToday comes out on top with real-time mid-market rates updated every 60 seconds, 160+ currencies, historical data on the free tier, and no credit card required.
Side-by-Side Comparison
| API | Free Tier | Historical Data | Currencies | Update Speed |
|---|---|---|---|---|
| AllRatesToday | Free, no CC | Yes, on free tier | 160+ | 60 seconds |
| ExchangeRate-API | 1,500 req/mo | Paid only | 160+ | Daily |
| Open Exchange Rates | 1,000 req/mo | Limited on free | 170+ | Hourly |
| Frankfurter | Unlimited | Yes (ECB only) | ~30 | Daily (ECB) |
| Fixer.io | 100 req/mo | Paid only | 170+ | Daily |
Key takeaway: AllRatesToday is the only API on this list with real-time rates updated every 60 seconds, historical data on the free tier, and no credit card requirement — behind a plain REST interface that Java's built-in HttpClient and Spring's RestClient handle without any custom plumbing.
1. AllRatesToday — Best Overall for Java and Spring Boot
AllRatesToday delivers real-time mid-market rates from institutional interbank market data for 160+ currencies. Authentication is a standard Authorization: Bearer header, and every endpoint returns clean JSON. Sign up at allratestoday.com/register for a free key (they look like art_live_...).
Get the latest rates (Java 11+ HttpClient)
GET /api/v1/rates returns an array of rate objects, one per target currency. This example uses Jackson for JSON:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
public class LatestRates {
record Rate(double rate, String source, String target, String time) {}
public static void main(String[] args) throws Exception {
String apiKey = System.getenv("ALLRATESTODAY_API_KEY");
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(
"https://allratestoday.com/api/v1/rates?source=USD&target=EUR,GBP,JPY"))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
ObjectMapper mapper = new ObjectMapper();
Rate[] rates = mapper.readValue(response.body(), Rate[].class);
for (Rate r : rates) {
System.out.printf("1 %s = %.4f %s (as of %s)%n",
r.source(), r.rate(), r.target(), r.time());
}
}
} Convert an amount with BigDecimal
Fetch the pair's rate, then do the arithmetic in BigDecimal — never double — so cents come out exact:
import java.math.BigDecimal;
import java.math.RoundingMode;
public BigDecimal convert(String from, String to, BigDecimal amount)
throws Exception {
String apiKey = System.getenv("ALLRATESTODAY_API_KEY");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(
"https://allratestoday.com/api/v1/rates?source="
+ from + "&target=" + to))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new IllegalStateException(
"API returned status " + response.statusCode());
}
Rate[] rates = new ObjectMapper()
.readValue(response.body(), Rate[].class);
BigDecimal rate = BigDecimal.valueOf(rates[0].rate());
return amount.multiply(rate)
.setScale(2, RoundingMode.HALF_EVEN);
}
// Usage:
// BigDecimal result = convert("USD", "EUR", new BigDecimal("1000.00"));
// System.out.println("$1,000.00 = €" + result); Fetch historical rates
GET /api/historical-rates returns a series of data points for a pair over a preset period (1d, 7d, 30d, or 1y):
record HistoricalPoint(String date, double rate, long timestamp) {}
record HistoricalResponse(
String source,
String target,
HistoricalPoint[] data,
String period
) {}
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(
"https://allratestoday.com/api/historical-rates?source=USD&target=EUR&period=30d"))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
ObjectMapper mapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
HistoricalResponse history =
mapper.readValue(response.body(), HistoricalResponse.class);
for (HistoricalPoint point : history.data()) {
System.out.printf("%s: %.4f%n", point.date(), point.rate());
} Spring Boot service with caching
In Spring Boot 3.2+, wrap the API in a service using RestClient and Spring's cache abstraction:
@Service
public class ExchangeRateService {
private final RestClient restClient;
public ExchangeRateService(
@Value("${allratestoday.api-key}") String apiKey) {
this.restClient = RestClient.builder()
.baseUrl("https://allratestoday.com")
.defaultHeader("Authorization", "Bearer " + apiKey)
.build();
}
@Cacheable(value = "rates", key = "#from + '_' + #to")
public BigDecimal getRate(String from, String to) {
Rate[] rates = restClient.get()
.uri("/api/v1/rates?source={from}&target={to}", from, to)
.retrieve()
.body(Rate[].class);
return BigDecimal.valueOf(rates[0].rate());
}
}
// application.yml:
// allratestoday:
// api-key: ${ALLRATESTODAY_API_KEY}
// spring:
// cache:
// caffeine:
// spec: expireAfterWrite=5m Docs: Full endpoint reference, parameters, and response schemas are on the AllRatesToday developer page.
- Data source: institutional interbank market data
- Update frequency: Every 60 seconds (real-time)
- Currencies: 160+ including majors, minors, and exotics
- Rate type: Mid-market (no bank markup)
- Free tier: Available — no credit card required
- Auth: Standard
Authorization: Bearerheader
2. ExchangeRate-API
ExchangeRate-API is simple and its free tier of 1,500 requests per month is relatively generous. But rates update only once a day and historical data requires a paid plan, which limits it to applications where a stale rate is acceptable.
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(
"https://v6.exchangerate-api.com/v6/YOUR_API_KEY/latest/USD"))
.GET().build();
// Parse: conversion_rates.EUR, time_last_update_utc - Free tier: 1,500 requests/month
- Update frequency: Daily
- Limitation: No real-time rates, historical data requires paid plan
3. Open Exchange Rates
Open Exchange Rates is a mature service, but the free plan is locked to USD as the base currency and updates only hourly. For a EUR-based European business, that means inverting USD pairs client-side or paying up.
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(
"https://openexchangerates.org/api/latest.json?app_id=YOUR_APP_ID&base=USD"))
.GET().build();
// Parse: rates (Map<String, Double>) - Free tier: 1,000 requests/month, USD base only
- Update frequency: Hourly on free, more frequent on paid
- Limitation: Free plan locked to USD base currency
4. Frankfurter
Frankfurter serves European Central Bank reference rates for free with no API key — ideal for tests and prototypes. But it covers only ~30 currencies, publishes once per business day at 16:00 CET, and has no weekend data.
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(
"https://api.frankfurter.app/latest?from=USD&to=EUR,GBP"))
.GET().build();
// Parse: date, rates (Map<String, Double>) - Free tier: Unlimited (no API key)
- Currencies: ~30 (ECB reference rates only)
- Limitation: No exotic currencies, no real-time data, no weekend updates
5. Fixer.io
Fixer has stagnated since its acquisition by APILayer. The free tier allows only 100 requests per month with EUR-only base, and worst of all serves HTTP without HTTPS — disqualifying for any serious Java application handling financial data.
- Free tier: 100 requests/month, EUR base only
- Update frequency: Daily
- Limitation: No HTTPS on free tier, very low request limit
Best Practices: Money and Caching in Java
Always use BigDecimal for money
double cannot represent 0.1 exactly, and the error compounds through arithmetic — a classic source of off-by-a-cent bugs in invoices. The rules:
- Construct from strings:
new BigDecimal("19.99"), nevernew BigDecimal(19.99)(the latter captures the binary error). - Convert API rates once at the boundary with
BigDecimal.valueOf(rate). - Round exactly once, at the end, with an explicit scale and
RoundingMode.HALF_EVEN(banker's rounding). - For multi-currency domain models, consider JSR 354 (
javax.money/ Moneta), which pairs an amount with its currency and prevents mixing units.
Cache aggressively
Rates change every 60 seconds at most, so there is no reason to call the API on every request. Spring's @Cacheable with a Caffeine TTL of 1–5 minutes (shown above) typically cuts request volume by well over 90%. For multi-instance deployments, back the cache with Redis so all instances share one upstream fetch. Our guide on caching exchange rates covers stampede protection and stale-while-revalidate patterns.
Frequently Asked Questions
What is the best exchange rate API for Java?
AllRatesToday is the best exchange rate API for Java and Spring Boot in 2026. Its REST API works with the built-in java.net.http.HttpClient (Java 11+), delivers real-time mid-market rates for 160+ currencies updated every 60 seconds, and offers a free tier with no credit card required.
How do I get exchange rates in Java?
Use Java 11's built-in HttpClient to call GET https://allratestoday.com/api/v1/rates?source=USD&target=EUR with an Authorization: Bearer header, then parse the JSON response with Jackson or Gson. In Spring Boot, RestClient or WebClient work the same way.
Is there a free currency API for Spring Boot applications?
Yes. AllRatesToday offers a free tier with no credit card required, covering real-time rates, conversion, and historical data. Frankfurter is also free but only covers about 30 ECB currencies with daily updates.
Should I use double for currency amounts in Java?
No. double is binary floating point and cannot represent decimal amounts exactly, which leads to rounding errors in money calculations. Always use java.math.BigDecimal with an explicit MathContext and RoundingMode for monetary arithmetic.
How often does AllRatesToday update its exchange rates?
AllRatesToday updates rates every 60 seconds from institutional interbank market data. Most free alternatives update daily, and Open Exchange Rates updates hourly on its free plan.
Start Building with Java and Real-Time Rates
Get your free API key in 30 seconds and fetch real-time mid-market rates for 160+ currencies with the built-in HttpClient or Spring's RestClient. Paid plans start at €4.99/month — see pricing for details.
Get Your Free API Key