Logo
New RPC users get 35% off their first monthView the offer
OnFinality Learn
Integration & Development13 min read

Solana getMultipleAccounts: Batch Account Reads

Read many Solana accounts in one JSON-RPC request with getMultipleAccounts, align ordered results safely, and cut duplicate round-trips.

TL;DR

getMultipleAccounts is the Solana JSON-RPC method that reads a bounded array of accounts by base58 pubkey in a single request and returns an array of account entries in the same order as the input. Each element is either the account object or null when the account does not exist at the requested commitment, and the result array length always equals the input length, so callers must align by index rather than filtering nulls. The response envelope carries context.slot, which identifies the snapshot the whole batch was read against, plus a value array. Choosing jsonParsed or base64 encoding changes payload size and client-side parsing work, and the per-request account bound is documented as bounded but varies by provider. This guide covers the method's semantics, a runnable Node.js example, a reproducible measurement table, limitations, and troubleshooting for off-by-one alignment and unexpected nulls.

The account-read problem in Solana clients

A wallet UI, indexer, or trading bot rarely needs one account. It needs a set: the user's token accounts, a handful of mint accounts, a few program-owned state accounts, and maybe a fee payer. The naive implementation loops over pubkeys and calls getAccountInfo once per account, which multiplies latency by the number of accounts and consumes one provider budget unit per call. On a page that refreshes every few seconds, that loop is the difference between a responsive UI and a queue of duplicate requests.

The single-account method is Solana getAccountInfo reference, and it remains the right tool when you genuinely need one account. The batch-read surface exists because the common case is many accounts at one point in time. If you are still building the single-account mental model, start with Reading Solana accounts: getAccountInfo and rent before layering batching on top.

The cost model matters here. Every JSON-RPC request you send is a unit of work for your endpoint, and providers meter requests rather than accounts. Replacing an N-call loop with one call that carries N pubkeys changes both the round-trip count and the budget footprint. The exact metering is documented per provider, so treat any specific number as provider-specific and verify against your plan.

  • N sequential getAccountInfo calls = N round-trips and N budget units.
  • One getMultipleAccounts call = one round-trip carrying N pubkeys.
  • The batch is read against a single context slot, which is a stronger consistency guarantee than N independent reads.

What getMultipleAccounts returns and why order is guaranteed

The Solana getMultipleAccounts reference (https://solana.com/docs/rpc/http/getmultipleaccounts) documents the method as taking an array of base58-encoded pubkeys and returning an array of account entries. The critical contract is positional: result value[i] corresponds to input pubkeys[i]. The array length equals the input length, and a missing account is represented as null at its index rather than being omitted.

That null-in-place behavior is the part most integrations get wrong. If you filter nulls before aligning, every index after the first missing account shifts and you silently attach the wrong account data to the wrong pubkey. The safe pattern is to iterate the input array by index and read value[i] for each i, treating null as a first-class outcome.

The response envelope wraps the array in a context object. The context.slot field identifies the bank slot the batch was read against, and because all accounts in one call share that slot, the batch is a consistent snapshot. That is a meaningful advantage over N separate getAccountInfo calls, which can land on different slots and produce a torn view of related state.

  • value.length === pubkeys.length, always.
  • value[i] is the account object for pubkeys[i], or null if not found.
  • context.slot is the snapshot identity for the entire batch.
  • Never drop nulls before zipping results back to inputs.

Encoding choice: jsonParsed versus base64

getMultipleAccounts accepts an encoding parameter that controls how account data is serialized. base64 returns the raw account bytes encoded as a string, which is compact and unambiguous but requires the client to deserialize. jsonParsed asks the node to decode known account layouts, such as SPL Token accounts, into structured JSON, which is convenient but produces larger payloads and depends on the node recognizing the account owner's layout.

The tradeoff is payload size versus client-side work. For a batch of a hundred token accounts, jsonParsed can be several times larger on the wire than base64, and the extra bytes are paid on every refresh. For a batch of program-owned accounts with custom layouts, jsonParsed may return only the raw data anyway, so base64 plus your own decoder is often the more predictable choice.

Encoding is a per-request parameter, so you can mix strategies across calls: use jsonParsed for a small, human-facing balance display and base64 for a high-frequency indexer loop. The method reference documents the accepted encodings; confirm which ones your provider enables, since support is documented but can vary by provider.

  • base64: smallest payload, client deserializes.
  • jsonParsed: structured output for recognized layouts, larger payload.
  • Encoding is per request, so different call sites can choose differently.

getMultipleAccounts versus getAccountInfo and getProgramAccounts

The three read surfaces answer different questions. getAccountInfo reads exactly one account by pubkey. getMultipleAccounts reads a bounded set of accounts by pubkey in one request. getProgramAccounts scans all accounts owned by a program, optionally filtered, which is a fundamentally different operation with different cost characteristics. The program-wide scan and its streaming counterpart are covered in getProgramAccounts indexer account stream.

Choose getMultipleAccounts when you already know the pubkeys and want them at one slot. Choose getProgramAccounts when you do not know the pubkeys and need discovery. Choose getAccountInfo when you need exactly one account and want the simplest possible call, or when you need a per-call commitment that differs from the rest of your batch.

There is a third option worth naming: a JSON-RPC 2.0 batch, which wraps multiple independent method calls in one transport request. The JSON-RPC 2.0 specification (https://www.jsonrpc.org/specification) defines this envelope, and it is the right tool when you need per-call parameters or per-call commitment. A batch of getAccountInfo calls gives you independent results and independent errors; one getMultipleAccounts call gives you a single ordered array and a single context slot. The transport-level mechanics are covered in JSON-RPC batching best practices.

  • getAccountInfo: one pubkey, one account, simplest call.
  • getMultipleAccounts: many pubkeys, one request, ordered array, one slot.
  • getProgramAccounts: discovery by program owner, not by known pubkey.
  • JSON-RPC batch: multiple independent calls, per-call parameters and errors.

Runnable Node.js example with @solana/web3.js

The example below uses @solana/web3.js, which exposes getMultipleAccountsInfo on a Connection. It builds a pubkey array that deliberately mixes an existing account with a nonexistent one, calls the method, prints the context slot, and then zips results back to inputs by index. The zip step is the part to copy into production code.

Replace the endpoint URL with your own RPC endpoint. The example prints present or null for each index so you can see the positional contract in action. Note that the nonexistent pubkey is a valid base58 string that simply has no account, which is exactly the case that produces a null rather than an error.

import { Connection, PublicKey } from '@solana/web3.js';

const connection = new Connection('https://your-solana-rpc-endpoint', 'confirmed');

// A real, well-known account plus a valid-but-nonexistent pubkey.
const existing = new PublicKey('11111111111111111111111111111111');
const missing = new PublicKey('So11111111111111111111111111111111111111112');
const inputs = [existing, missing];

const res = await connection.getMultipleAccountsInfo(inputs);

console.log('context slot:', res.context.slot);
res.value.forEach((account, i) => {
  const label = account ? 'present' : 'null';
  console.log(`index ${i} ${inputs[i].toBase58()} -> ${label}`);
});

// Safe zip: iterate inputs by index, never filter nulls first.
const zipped = inputs.map((pubkey, i) => ({
  pubkey: pubkey.toBase58(),
  account: res.value[i] ?? null,
}));
console.log(zipped);

Runnable raw JSON-RPC example over fetch

If you are not using @solana/web3.js, the same call is a plain JSON-RPC POST. The params array is [pubkeys, options], where options carries the encoding and commitment. The response shape is the standard JSON-RPC result envelope with context and value.

This example uses base64 encoding and prints the slot plus a present/null summary. It is intentionally minimal so you can paste it into a script and point it at any endpoint. The method reference documents the exact parameter order; keep the pubkey array first and the options object second.

const endpoint = 'https://your-solana-rpc-endpoint';

const body = {
  jsonrpc: '2.0',
  id: 1,
  method: 'getMultipleAccounts',
  params: [
    [
      '11111111111111111111111111111111',
      'So11111111111111111111111111111111111111112'
    ],
    { encoding: 'base64', commitment: 'confirmed' }
  ]
};

const response = await fetch(endpoint, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body)
});

const json = await response.json();
if (json.error) throw new Error(JSON.stringify(json.error));

const { context, value } = json.result;
console.log('context slot:', context.slot);
value.forEach((account, i) => {
  console.log(`index ${i} -> ${account ? 'present' : 'null'}`);
});

Reproducible measurement: filling your own results table

Provider behavior, network conditions, and account sizes all affect real numbers, so the honest approach is to measure against your own endpoint rather than trust a published figure. The table below is a template. Run the same workload twice: once as an N-call getAccountInfo loop, once as a single getMultipleAccounts call, and record the observed values.

Use a fixed pubkey set and a fixed commitment so the comparison is fair. Run each variant several times and record the median rather than a single sample, because the first call after a cold start is not representative. If your provider exposes request counters, record the budget units consumed as well as wall time.

  • Input count: number of pubkeys in the batch.
  • Request count: 1 for getMultipleAccounts, N for the loop.
  • Wall time single-loop: median across repeated runs.
  • Wall time getMultipleAccounts: median across repeated runs.
  • Nulls encountered: count of null entries in the result.
  • Context slot: the slot reported for the batch.

Limitations and tradeoffs of batch account reads

The per-request account bound is documented as bounded but varies by provider, so a batch that works on one endpoint may be rejected on another. The commonly cited ceiling is on the order of a hundred pubkeys, but treat that as a starting point for verification, not a guarantee. If you need more accounts than the bound allows, split into multiple calls and accept that they may land on different slots.

A malformed argument fails the whole call. If one pubkey in the array is not valid base58, the request errors rather than returning a null at that index, so validate inputs before sending. This is different from a valid pubkey with no account, which produces a null.

A null means not found at that slot, not an empty account. An account can exist at one slot and be absent at another, so a null is a statement about the requested commitment and slot, not a permanent property of the pubkey. Very large batches may also exceed provider request-size limits even when the account count is within bounds, because payload size depends on account data length and encoding.

  • Per-request account bound: documented, varies by provider.
  • One invalid pubkey fails the entire call.
  • null means not found at that slot, not an empty account.
  • Large batches can hit request-size limits independent of account count.

Troubleshooting off-by-one alignment and unexpected nulls

Off-by-one alignment almost always comes from filtering or sorting the result array before zipping. If you call value.filter(Boolean) and then index into the filtered array, every entry after the first null is misaligned. The fix is to keep the original array and read value[i] for each input index, as shown in the examples above.

Unexpected nulls usually mean the account does not exist at the requested commitment, or the pubkey is correct but the account was closed. Check the context slot and re-read at a different commitment if you suspect a race. If a null appears for an account you believe exists, verify the pubkey encoding and confirm you are querying the intended cluster.

Size-limit errors surface as JSON-RPC errors rather than partial results. If a batch is rejected, reduce the account count, switch from jsonParsed to base64 to shrink the payload, or split into multiple calls. For indexers that must not miss slots, pair batch reads with gap detection as described in getBlocks and skipped slots for gap-free indexing.

  • Misalignment: caused by filtering or sorting before zipping.
  • Unexpected null: check commitment, slot, cluster, and pubkey encoding.
  • Size-limit error: reduce count, switch encoding, or split the batch.

Next steps for production batch reads

Start by replacing your hottest N-call loop with a single getMultipleAccounts call, then measure the change with the results table above. Keep the single-account path for cases that genuinely need one account, and keep a JSON-RPC batch path for cases that need per-call parameters or per-call commitment.

For endpoint selection and network details, see the Solana network page. For a broader orientation on Solana RPC usage, the Solana API guide (RPC Assistant) covers endpoints and common methods. When you are ready to size a plan around your measured request volume, review RPC pricing and the API service options.

Finally, browse the OnFinality Learn hub for related integration guides. The batch-read surface is one piece of a larger read strategy that includes single-account reads, program scans, and gap-free indexing.

  • Replace hot N-call loops with one getMultipleAccounts call.
  • Keep getAccountInfo for single-account reads.
  • Keep JSON-RPC batches for per-call parameters and errors.
  • Measure before and after with your own results table.

Never Worry about Infrastructure Again

OnFinality takes away the heavy lifting of DevOps so you can build smarter and faster.

Get Started