By language & tool

How do I get fund or ETF data by ISIN in Java?

Working Java example that turns an ISIN into fund name, TER, risk indicator, holdings, sector and country weights and returns as JSON. Free key, no card.Updated 12 September 2026 · by FundFacts API

Short answer

Send GET https://fundfactsapi.com/api/v1/funds/{isin} with the header Authorization: Bearer <your key> and parse the JSON. The body holds the fund's name plus a data object with fees (headlineMetrics.ter), the 1–7 risk indicator (riskRating), top holdings, sector and country weights, returns and the as-of date. The Java snippet below does exactly that; a free key (15 lookups a month, no card) is enough to run it.

What you need

  • An API key from fundfactsapi.com/signup. The Free plan is 15 lookups a month and asks for no card. Keys start with ffk_.
  • The ISIN of the share class you want. Every share class has its own ISIN; GET /search?q= turns a name into ISINs and is free.
  • java.net.http.HttpClient (Java 11+) for the request and Jackson for parsing; any JSON library works because the payload is plain JSON.

The code

java
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
import com.fasterxml.jackson.databind.*;
public class FundLookup {
public static void main(String[] args) throws Exception {
String key = System.getenv("FUNDFACTS_API_KEY");
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://fundfactsapi.com/api/v1/funds/IE00B4L5Y983"))
.header("Authorization", "Bearer " + key)
.timeout(Duration.ofSeconds(300)) // first load of a cold ISIN can take minutes
.GET().build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
if (res.statusCode() != 200) throw new RuntimeException("HTTP " + res.statusCode() + ": " + res.body());
JsonNode fund = new ObjectMapper().readTree(res.body());
JsonNode d = fund.get("data");
System.out.println(fund.get("name").asText());
System.out.println("TER " + d.get("headlineMetrics").get("ter").asText());
System.out.println("Risk " + d.get("riskRating").asInt() + " / 7");
for (JsonNode h : d.get("topHoldings")) {
System.out.println(h.get("name").asText() + " " + h.get("weight").asDouble() + "%");
}
System.out.println("as of " + d.get("dataAsOf").asText());
}
}

What comes back

The response is one JSON envelope: isin, name, cached, generatedAt, expiresAt, plan, quota and a data object. Inside data you get keyFacts (asset class, currency, fund size, inception date, distribution policy, number of holdings), headlineMetrics (TER, AUM, 3-year volatility and Sharpe, yield to maturity and duration for bond funds), riskRating (the 1–7 KID indicator), profile (a rule-based classification: kind, category, risk band, region focus, sector tilt), topHoldings, sector, geography, region, assetAllocation, creditQuality, maturity, calendarReturns, annualisedReturns, cumulativePerformance, indexedPerformance, metrics (max drawdown, P/E, income yield…), sfdrArticle, costs (PRIIPs entry, exit, ongoing, transaction and performance fees, reduction in yield) and dataAsOf, the date of the underlying figures.

Money and percentages arrive as the fund house prints them, as strings such as "0.20%" or "USD 151.7bn". Breakdown weights and return series are plain numbers in percent. Fields that do not apply to the fund's asset class are empty strings, null or empty arrays, never missing keys; treat empty as "not disclosed", not as zero.

A trimmed example for iShares Core MSCI World UCITS ETF (IE00B4L5Y983):

json
{
"isin": "IE00B4L5Y983",
"name": "iShares Core MSCI World UCITS ETF",
"cached": true,
"expiresAt": "2026-09-13T06:00:00.000Z",
"data": {
"keyFacts": { "assetClass": "Equities", "currency": "USD", "aum": "USD 151.7bn", "inception": "2009-09-25", "distribution": "Accumulating", "holdings": 1253 },
"headlineMetrics": { "ter": "0.20%", "volatility3y": "11.8%", "sharpe3y": "1.68" },
"riskRating": 6,
"profile": { "kind": "equity", "category": "Global Equity", "riskBand": "high", "regionFocus": "Global", "sectorTilt": "Broad" },
"topHoldings": [ { "name": "NVIDIA", "weight": 5.48 }, { "name": "APPLE", "weight": 5.24 } ],
"sector": [ { "label": "Information Technology", "weight": 29.8 }, { "label": "Financials", "weight": 16.46 } ],
"geography": [ { "label": "United States", "weight": 72.07 }, { "label": "Japan", "weight": 5.85 } ],
"calendarReturns": { "years": ["2023", "2024", "2025"], "fund": [23.9, 18.7, 21.2], "benchmark": [23.8, 18.7, 21.1] },
"metrics": { "maxDrawdown": "-16.5%" },
"dataAsOf": "2026-09-01"
}
}

Things to know before shipping

First call is slow, later calls are fast. A fund nobody has requested in the last 24 hours is loaded on demand from the documents the fund house publishes. That first call takes 15 seconds to about 3 minutes; set your client timeout to 300 seconds and do not fire a second request for the same ISIN while the first is running. Every later call within 24 hours returns from the store in well under a second with cached: true.

Requests are counted per ISIN. One request is counted per ISIN answered. X-RateLimit-Remaining on every response tells you how many are left this month; a 429 rate_limited response carries Retry-After. Search and /me are free. Cache payloads by ISIN until expiresAt and you will rarely spend more than one request per fund per day.

Batch when you have a list. POST /funds with { "isins": [...], "wait": true } answers up to 10 ISINs per call on Starter, 50 on Pro and 200 on Scale. ISINs still loading come back with status: "pending"; send them again a minute later.

Not every ISIN is a fund. A stock, bond or index ISIN returns 404 fund_not_found. Validate the format first (12 characters, Luhn check digit) so a typo does not spend a request; the ISIN validator shows the rule.

Show the as-of date. data.dataAsOf is the date printed on the fund's documents. Display it next to any figure.

Going further

Verify it yourself

The demo endpoint returns the live payload for a fund that is already in the store, without a key. Everything on this page can be checked against it.
bash
curl -s https://fundfactsapi.com/api/v1/demo/funds/IE00B4L5Y983 | jq '{name, asOf: .data.dataAsOf, ter: .data.headlineMetrics.ter, risk: .data.riskRating, top: .data.topHoldings[:3]}'

Frequently asked questions

Do I need a credit card to try it?

No. The Free plan is 15 lookups per month with no card; sign up with an e-mail address or Google and the key is shown in the dashboard.

Which ISINs work?

Any share class of a UCITS fund, ETF, money-market fund or open-end fund whose management company publishes a product page, factsheet and KID. Each share class has its own ISIN and is looked up on its own. Stocks, bonds and indices are not funds and return 404 fund_not_found.

How current are the figures?

Each ISIN is re-read from the fund house's documents at most every 24 hours. data.dataAsOf is the as-of date printed on the documents, typically the last month end for a factsheet and the last trading day for holdings files.

Try it on your own ISINs

One request returns key facts, holdings, risk and performance as JSON. Free plan, no card.