JSON-RPC Error Handling Example
This example demonstrates how to implement proper error handling in JSON-RPC 2.0 applications, including standard error codes, custom error objects, and client-side error handling strategies.
What You'll Learn
- Standard JSON-RPC 2.0 error codes and their meanings
- How to structure error objects according to the specification
- Creating and using custom error codes for your application
- Server-side error handling best practices
- Client-side error handling techniques
JSON-RPC Error Handling Examples
Learn how to properly handle errors in JSON-RPC implementations with comprehensive examples.
Standard Error Codes
JSON-RPC 2.0 defines standard error codes for common error scenarios. Here are the predefined error codes:
Parse Error (-32700)
Invalid JSON was received by the server.
{
"jsonrpc": "2.0",
"error": {
"code": -32700,
"message": "Parse error"
},
"id": null
}
Invalid Request (-32600)
The JSON sent is not a valid Request object.
{
"jsonrpc": "2.0",
"error": {
"code": -32600,
"message": "Invalid Request"
},
"id": null
}
Method Not Found (-32601)
The method does not exist / is not available.
{
"jsonrpc": "2.0",
"error": {
"code": -32601,
"message": "Method not found"
},
"id": "1"
}
Invalid Params (-32602)
Invalid method parameter(s).
{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"parameter": "amount",
"expected": "number",
"received": "string"
}
},
"id": "1"
}
Internal Error (-32603)
Internal JSON-RPC error.
{
"jsonrpc": "2.0",
"error": {
"code": -32603,
"message": "Internal error",
"data": {
"timestamp": "2024-01-15T10:30:00Z",
"details": "Database connection timeout"
}
},
"id": "1"
}
Custom Error Codes
You can define custom error codes for application-specific errors. Use codes outside the reserved range (-32768 to -32000).
Example: Authentication Error
{
"jsonrpc": "2.0",
"error": {
"code": -1001,
"message": "Authentication failed",
"data": {
"reason": "Invalid API key",
"required_permissions": ["read", "write"]
}
},
"id": "1"
}
Example: Business Logic Error
{
"jsonrpc": "2.0",
"error": {
"code": -2001,
"message": "Insufficient funds",
"data": {
"account_balance": 50.00,
"requested_amount": 100.00,
"currency": "USD"
}
},
"id": "1"
}
Client-Side Error Handling
JavaScript Client Example
class JsonRpcClient {
async call(method, params, id = 1) {
try {
const response = await fetch(this.endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
method,
params,
id
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
if (data.error) {
// Handle JSON-RPC errors
const error = new JsonRpcError(
data.error.message,
data.error.code,
data.error.data
);
throw error;
}
return data.result;
} catch (error) {
if (error instanceof JsonRpcError) {
// Handle specific JSON-RPC errors
this.handleJsonRpcError(error);
} else {
// Handle network or other errors
this.handleNetworkError(error);
}
throw error;
}
}
handleJsonRpcError(error) {
switch (error.code) {
case -32601:
console.error('Method not available:', error.message);
break;
case -32602:
console.error('Invalid parameters:', error.data);
break;
case -1001:
console.error('Authentication failed:', error.data);
// Redirect to login page
break;
default:
console.error('JSON-RPC Error:', error);
}
}
handleNetworkError(error) {
console.error('Network error:', error.message);
// Show user-friendly error message
this.showNotification('Connection error. Please try again.');
}
}
class JsonRpcError extends Error {
constructor(message, code, data) {
super(message);
this.name = 'JsonRpcError';
this.code = code;
this.data = data;
}
}
Python Client Example
import json
import requests
from typing import Any, Dict, Optional
class JsonRpcError(Exception):
def __init__(self, message: str, code: int, data: Optional[Any] = None):
super().__init__(message)
self.code = code
self.data = data
class JsonRpcClient:
def __init__(self, endpoint: str):
self.endpoint = endpoint
self.session = requests.Session()
def call(self, method: str, params: Any = None, request_id: int = 1) -> Any:
payload = {
'jsonrpc': '2.0',
'method': method,
'id': request_id
}
if params is not None:
payload['params'] = params
try:
response = self.session.post(
self.endpoint,
json=payload,
headers={'Content-Type': 'application/json'},
timeout=30
)
response.raise_for_status()
data = response.json()
if 'error' in data:
error = data['error']
raise JsonRpcError(
error['message'],
error['code'],
error.get('data')
)
return data.get('result')
except requests.RequestException as e:
self._handle_network_error(e)
raise
except JsonRpcError as e:
self._handle_jsonrpc_error(e)
raise
def _handle_jsonrpc_error(self, error: JsonRpcError):
if error.code == -32601:
print(f"Method not available: {error.message}")
elif error.code == -32602:
print(f"Invalid parameters: {error.data}")
elif error.code == -1001:
print(f"Authentication failed: {error.data}")
else:
print(f"JSON-RPC Error {error.code}: {error.message}")
def _handle_network_error(self, error: requests.RequestException):
print(f"Network error: {error}")
# Log error for debugging
# Show user-friendly message
Server-Side Error Handling
Node.js Server Example
const express = require('express');
const app = express();
app.use(express.json());
// Error response helper
function createErrorResponse(id, code, message, data = null) {
return {
jsonrpc: '2.0',
error: {
code,
message,
...(data && { data })
},
id
};
}
// JSON-RPC handler
app.post('/jsonrpc', async (req, res) => {
let requestId = null;
try {
// Parse request
const request = req.body;
requestId = request.id;
// Validate JSON-RPC format
if (request.jsonrpc !== '2.0') {
return res.json(createErrorResponse(
requestId,
-32600,
'Invalid Request'
));
}
if (!request.method) {
return res.json(createErrorResponse(
requestId,
-32600,
'Invalid Request',
{ missing: 'method' }
));
}
// Method routing
const methods = {
'add': handleAdd,
'subtract': handleSubtract,
'getUser': handleGetUser
};
const handler = methods[request.method];
if (!handler) {
return res.json(createErrorResponse(
requestId,
-32601,
'Method not found',
{ available_methods: Object.keys(methods) }
));
}
// Execute method
const result = await handler(request.params);
// Success response
res.json({
jsonrpc: '2.0',
result,
id: requestId
});
} catch (error) {
console.error('JSON-RPC Error:', error);
// Handle different error types
if (error.name === 'ValidationError') {
res.json(createErrorResponse(
requestId,
-32602,
'Invalid params',
{ validation_errors: error.details }
));
} else if (error.name === 'AuthenticationError') {
res.json(createErrorResponse(
requestId,
-1001,
'Authentication failed',
{ reason: error.message }
));
} else {
res.json(createErrorResponse(
requestId,
-32603,
'Internal error',
process.env.NODE_ENV === 'development' ? { stack: error.stack } : null
));
}
}
});
async function handleAdd(params) {
if (!params || typeof params.a !== 'number' || typeof params.b !== 'number') {
const error = new Error('Parameters must be numbers');
error.name = 'ValidationError';
error.details = {
expected: { a: 'number', b: 'number' },
received: params
};
throw error;
}
return params.a + params.b;
}
async function handleGetUser(params) {
if (!params || !params.id) {
const error = new Error('User ID is required');
error.name = 'ValidationError';
error.details = { missing: 'id' };
throw error;
}
// Simulate user lookup
const user = await getUserById(params.id);
if (!user) {
const error = new Error('User not found');
error.name = 'NotFoundError';
throw error;
}
return user;
}
Error Handling Best Practices
1. Use Standard Error Codes
Always use the predefined error codes for common scenarios. This ensures consistency and makes it easier for clients to handle errors.
2. Provide Detailed Error Data
Include helpful information in the error data field, such as validation details, expected formats, or suggestions for fixing the error.
3. Don't Leak Sensitive Information
Be careful not to expose internal system details, database schemas, or sensitive data in error messages. Use generic messages for production.
4. Log Errors for Debugging
Always log detailed error information on the server side for debugging purposes, even if you return generic error messages to clients.
5. Handle Batch Request Errors
For batch requests, return individual error responses for each failed request rather than failing the entire batch.