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.