A JSON-RPC batch request is an array of request objects sent in a single HTTP POST. Each request must have a unique id, and the server returns an array of responses in the same order, with per-item errors. Batching reduces round-trips and is ideal for multiple independent reads, but it does not reduce server load or guarantee atomicity. For eth_getLogs, batching pairs with pagination to scan large ranges efficiently.
What Is a JSON-RPC Batch Request?
A JSON-RPC batch request is a single HTTP POST where the body is a JSON array containing multiple request objects. Each object follows the standard JSON-RPC 2.0 structure: jsonrpc, method, params, and id. The server processes all requests and returns a JSON array of response objects, one per request, in the same order. This is defined in the JSON-RPC 2.0 Specification.
For Ethereum and other EVM chains, the JSON-RPC API exposed by providers like OnFinality supports batching. Instead of sending 10 separate eth_blockNumber calls, you send one request with 10 items. This reduces network overhead and can significantly lower latency for applications that need multiple independent data points.
- Batch requests are arrays:
[ {...}, {...} ] - Each item must have a unique
id(number or string) - The response is an array of results/errors, in the same order as the request
- If the entire batch is invalid (e.g., empty array), the server returns a single error object
curl -X POST https://eth.api.onfinality.io/public \
-H 'Content-Type: application/json' \
-d '[
{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1},
{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":2},
{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x742d35Cc6634C0532925a3b844Bc454e4438f44e","latest"],"id":3}
]'Anatomy of a Batch Request and Response
Each request object in the batch must be a valid JSON-RPC request. The id is used to correlate responses, but since the server returns responses in the same order, you can rely on positional matching. However, the spec recommends unique ids for clarity and for debugging.
The response is an array of response objects. Each response has either a result or an error field. If a request fails (e.g., method not found, invalid params), the server still returns a response object with an error and a null result, and the other requests are unaffected. This is called error isolation.
Here is an example response for the batch above. Note that the third request uses an invalid address, so it returns an error, but the first two succeed.
[
{"jsonrpc":"2.0","result":"0x134a5c","id":1},
{"jsonrpc":"2.0","result":"0x1","id":2},
{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid argument 0: hex string has length 42, want 40 for common.Address"},"id":3}
]When to Use Batch Requests (and When Not To)
Batching shines when you have multiple independent read calls that can be executed in parallel. For example, fetching balances for several addresses, getting block details for multiple blocks, or checking transaction receipts. By combining them into one HTTP request, you reduce the number of round-trips from N to 1, which is especially beneficial on high-latency connections.
However, batching does not reduce the server-side workload. Each request is processed individually, so the total compute is the same. It also does not provide atomicity: if one request fails, the others still execute. If you need to ensure all-or-nothing behavior, you must handle that at the application level.
Avoid batching for dependent calls where the result of one is needed as input to another. For example, you cannot batch an eth_getTransactionCount and then an eth_sendRawTransaction that depends on that nonce. You must wait for the first response.
Also, be mindful of batch size. Most providers, including OnFinality, impose a maximum number of requests per batch (often 100-200). Sending a huge batch may result in a 413 Payload Too Large or a JSON-RPC error. Check your provider's documentation or test with a small batch first.
- Good: multiple
eth_getBalancecalls, multipleeth_getBlockByNumbercalls,eth_getLogsfor different ranges - Bad: dependent calls, writes that require a nonce, calls that need to be sequential
- Batch size limits: keep under 100 items to be safe, or check provider limits
Batching eth_getLogs for Pagination
eth_getLogs is a powerful but potentially expensive call. It returns all logs matching a filter, and if the range is too large, the node may time out or return an error like query returned more than 10000 results. To handle large ranges, you need to paginate: split the range into smaller chunks and fetch logs for each chunk.
Batching works perfectly with pagination. Instead of sending each chunk sequentially, you can send multiple eth_getLogs requests in a single batch, each with a different fromBlock and toBlock. This parallelizes the scan and reduces the total time.
For example, to scan blocks 1,000,000 to 1,000,999 for a specific contract, you could split into 10 chunks of 100 blocks each and batch them. The responses will contain logs for each chunk, which you can concatenate in order.
This approach is documented in community resources like Chainstack's guide on eth_getLogs limitations and sqd.dev's article on eth_getLogs pagination.
curl -X POST https://eth.api.onfinality.io/public \
-H 'Content-Type: application/json' \
-d '[
{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0xf4240","toBlock":"0xf42c0","address":"0x..."}],"id":1},
{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0xf42c0","toBlock":"0xf4340","address":"0x..."}],"id":2},
{"jsonrpc":"2.0","method":"eth_getLogs","params":[{"fromBlock":"0xf4340","toBlock":"0xf43c0","address":"0x..."}],"id":3}
]'Batch vs. Multicall: Which One to Use?
Multicall is a smart contract that aggregates multiple eth_call requests into a single call. It is often compared to batching because both reduce round-trips. However, they are fundamentally different.
Batch requests are processed by the node's JSON-RPC server, and each call is executed independently. Multicall executes multiple contract calls within a single EVM execution, which can be more efficient for read-only calls because it avoids the overhead of multiple JSON-RPC invocations. However, Multicall requires deploying or using a known Multicall contract address, and it only works for eth_call (not for eth_getBalance or eth_getLogs).
In practice, for simple balance checks or contract reads, batching is simpler and more flexible. For complex aggregations of many contract calls, Multicall can be faster because it reduces the number of EVM executions. The choice depends on your use case. If you need to call the same contract multiple times with different parameters, Multicall is often better. If you need to mix different methods (e.g., eth_getBalance and eth_call), batching is the way to go.
For more on optimizing RPC calls, see our guide on how to reduce RPC latency.
Diagnosing Batch Request Failures
When a batch request fails, the entire response may be a single error object if the batch itself is malformed (e.g., empty array, invalid JSON). If individual requests fail, you get per-item errors. Common errors include -32600 (Invalid Request) for missing jsonrpc or method, -32601 (Method not found) for typos, and -32602 (Invalid params) for wrong parameter types.
To diagnose, start by testing a single request to ensure it works. Then test a batch of two. Use curl -w to measure timing and see if batching actually improves latency. For example:
curl -w 'Total time: %{time_total}s\n' -X POST ... -d '[single]' vs. -d '[batch]'.
If you see a 413 Payload Too Large, reduce the batch size. If you see -32005 (limit exceeded) from the provider, you may be hitting rate limits or batch size limits. Check the OnFinality RPC Assistant for endpoint-specific guidance.
# Measure latency for a single request
curl -w 'Single: %{time_total}s\n' -X POST https://eth.api.onfinality.io/public \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
# Measure latency for a batch of 5
curl -w 'Batch: %{time_total}s\n' -X POST https://eth.api.onfinality.io/public \
-H 'Content-Type: application/json' \
-d '[
{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1},
{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":2},
{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":3},
{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":4},
{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":5}
]'Limitations and Trade-offs of Batching
While batching reduces network overhead, it does not reduce the total server load. Each request in the batch is processed individually, so a batch of 100 eth_getLogs calls is just as expensive as 100 separate calls. Providers may throttle or reject large batches to protect their infrastructure.
Another trade-off is error handling. With batching, you must parse each response individually and handle partial failures. This adds complexity to your code. Also, if one request in the batch is malformed, the server may reject the entire batch (depending on the implementation). The JSON-RPC spec says that if the batch is invalid (e.g., empty array), the server returns a single error object, but if individual items are invalid, they are handled separately.
Finally, batching does not guarantee ordering of execution on the server. The spec says the server may execute requests in any order, but responses are returned in the order of the requests. For read-only calls, this is fine, but for writes, you must not rely on order.
For more on handling timeouts and errors, see how to fix RPC timeout errors.
Next Steps: Optimize Your RPC Usage
Now that you understand batch requests, you can apply them to your dApp or script to reduce latency and improve efficiency. Start by identifying independent calls that can be batched, and measure the improvement using curl -w.
If you are building on Ethereum, Polygon, or Base, check the respective network pages for endpoint details: Ethereum, Polygon, Base. For a comprehensive list of endpoints, see our multi-chain RPC endpoints guide.
If you need higher throughput or dedicated infrastructure, consider our RPC pricing plans or the API service for advanced features. And for historical data access, read our guide on accessing historical blockchain data.