Best Exchange Rate API for Java and Spring Boot in 2026

Reviewed by Madhushan, Fintech Developer — August 2026
Developer writing Java code on a laptop

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.

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

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>)

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>)

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.

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:

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

Related Articles