JSON-RPC 2.0 Base Java Classes

  • Minimal implementation, just the base message classes,
    with parsing and serialisation support
  • No framework or transport layer dependencies
  • Batching deliberately left out

1. Briefly about this package

This package provides you with a set of Java classes to furnish a JSON-RPC 2.0 interface to your web application or service. Kept simple and minimalist, it doesn't impose any particular framework or transport mechanism onto you. You get seven classes to parse, represent and serialise JSON-RPC 2.0 messages and handle the respective exceptions.

2. What is JSON-RPC and what is it good for?

JSON-RPC is a protocol that can help you standardise communication between your browser JavaScript applications (AJAX or Web 2.0 if you prefer the jargon) and your backend logic and web services. It was conceived in 2004 as an alternative to XML-RPC, another remote-procedure-call (RPC) protocol using XML to encode requests and responses. JSON-RPC uses JSON strings to encode data which makes it more natural to work with from within JavaScript.

As of today (2009-2010) JSON-RPC is still to gain wider adoption. The lag can probably be explained with the fact that creating your own ad-hoc JSON-based data interchange scheme is easy. There are, however, strong reasons to consider a more standardised approach:

3. Version 2.0 of the JSON-RPC protocol

This package implements version 2.0 of the protocol (but see more on batching below). Explaining the protocol is beyond the scope of this manual, however, I'll list the two essential advances over the original version 1.0:

Here is an example version 2.0 JSON-RPC request (with pretty formatting applied):

{ 
  "method"  : "accounts.addUser",
  "params"  : { "name" : "Jason Remote", "age" : 25, "email" : "jason@remote.com" },
  "id"      : 123,
  "jsonrpc" : "2.0"
}

And here is an example response:

{
  "result" : "uid-001",
  "id"     : 123,
  "jsonrpc":"2.0"
}

The JSON-RPC 2.0 specification also allows sending of batch/multicall requests. The rationale for this feature is to improve RPC performance over HTTP connections. However, judging by user posts in the JSON-RPC forum, this tends to confuse people and so it was deliberately left out in this implementation.

The original JSON-RPC website is at json-rpc.org. Discussions and more recent standardisation efforts are at groups.google.com/group/json-rpc.

4. Installation

Requirements:

Download the package and unzip its content. Place the JAR file jsonrpc2base.jar in your CLASSPATH and you're ready to go.

Alternatively, you can rebuild the package yourself using Apache Ant. The full source code is located in the src/ directory.

5. The life cycle of a JSON-RPC 2.0 request

The table below shows the life cycle of a JSON-RPC 2.0 request and the Java classes/methods typically used at each step:

Step Side Action Used methods
1. Client Create a new request JSONRPC2Request()
2. Client Serialise request to string and send JSONRPC2Request.toString()
3. Server Parse received string back to request object JSONRPC2Request.parse()
4. Server Get the request data JSONRPC2Request.getMethod()
JSONRPC2Request.getParamsType()
JSONRPC2Request.getParams()
JSONRPC2Request.getID()
5. Server Create a response JSONRPC2Response()
6. Server Serialise response to string and send back JSONRPC2Response.toString()
7. Client Parse received string back to response object JSONRPC2Response.parse()
8. Client Check the response for success, get the result/error JSONRPC2Response.indicatesSuccess()
JSONRPC2Response.getResult()
JSONRPC2Response.getError()

6. Example usage

Let's now go through a JSON-RPC 2.0 example where the client sends a request to the server to make a payment on the user's behalf. Upon success the server returns a response containing a string with the payment transaction ID.

First we need to import the necessary packages.

// The JSON-RPC 2.0 classes
import com.thetransactioncompany.jsonrpc2.*;

// We'll need the standard Map and HashMap classes too
import java.util.*;

On the client side we create a request object, which we then serialise to a string to be sent out to the server using whatever transport mechanism is required (usually HTTP):

// The remote method to call
String method = "makePayment";

// The required named parameters to pass
Map params = new HashMap();
params.put("recipient", "Penny Adams");
params.put("amount", 175.05);

// The mandatory request ID
String id = "req-001";

// Create a new JSON-RPC 2.0 request
JSONRPC2Request reqOut = new JSONRPC2Request(method, params, id);

// Serialise the request to a JSON-encoded string
String jsonString = reqOut.toString();

// jsonString can now be dispatched to the server...

Here is the resulting JSON-encoded request string (pretty formatting applied extra):

{ 
  "id"      : "req-001",
  "method"  : "makePayment",
  "params"  : { "amount" : 175.05, "recipient" : "Penny Adams" },
  "jsonrpc" : "2.0"
}

On the server side, after receiving request string we proceed like this:

// Parse request string
JSONRPC2Request reqIn = null;

try {
	reqIn = JSONRPC2Request.parse(jsonString);
	
} catch (JSONRPC2ParseException e) {
	System.out.println(e.getMessage());
	// Handle exception...
}

// How to extract the request data
System.out.println("Parsed request with properties :");
System.out.println("\tmethod     : " + reqIn.getMethod());
System.out.println("\tparameters : " + reqIn.getParams());
System.out.println("\tid         : " + reqIn.getID() + "\n\n");

// Process request...

// Create the response (note that the JSON-RPC ID must be echoed back)
String result = "payment-id-001";
JSONRPC2Response respOut = new JSONRPC2Response(result, reqIn.getID());

// Serialise response to JSON-encoded string
jsonString = respOut.toString();

// The response string can now be sent back to the client...

The response string:

{ 
  "id"      : "req-001",
  "result"  : "payment-id-001",
  "jsonrpc" : "2.0"
}

Back on the client side we parse the response and check whether it was successful or an error was reported:

// Parse response string
JSONRPC2Response respIn = null;

try {
	respIn = JSONRPC2Response.parse(jsonString);
} catch (JSONRPC2ParseException e) {
	System.out.println(e.getMessage());
	// Handle exception...
}


// Check for success or error
if (respIn.indicatesSuccess()) {

	System.out.println("The request succeeded :");

	System.out.println("\tresult : " + respIn.getResult());
	System.out.println("\tid     : " + respIn.getID());
}
else {
	System.out.println("The request failed :");

	JSONRPC2Error err = respIn.getError();

	System.out.println("\terror.code    : " + err.getCode());
	System.out.println("\terror.message : " + err.getMessage());
	System.out.println("\terror.data    : " + err.getData());
}

You can download the complete example from here.

7. Generic parsing of JSON-RPC 2.0 messages

The above example assumed that the server is expecting only JSON-RPC requests. However, the protocol also defines another type of messages - notifications. They can be used in the following situation:

To parse JSON-RPC 2.0 messages of all types - requests, notifications and responses - use the static parse() method of the base abstract class JSONRPC2Message. The classes JSONRPC2Request, JSONRPC2Notification and JSONRPC2Response inherit from it.

Here is an example illustrating how you could do this:

JSONRPC2Message msg = null;
		
try {
	msg = JSONRPC2Message.parse(jsonString);

} catch (JSONRPC2ParseException e) {
	System.out.println(e);
	// handle exception
}

if (msg instanceof JSONRPC2Request) {
	System.out.println("The message is a Request");
}
else if (msg instanceof JSONRPC2Notification) {
	System.out.println("The message is a Notification");
}
else if (msg instanceof JSONRPC2Response) {
	System.out.println("The message is a Response");
}

The complete example demonstrating combined parsing of JSON-RPC messages can be downloaded from here.

8. Utility classes to extract positional and named parameters

In addition to the base classes offered here, you also get two utility classes to ease the retrieval of the paramaters once the request is received and correctly identified (by its method name):

There are getter methods that correspond to each JSON type, which are then split into two variants - one for retrieving mandatory parameters (will raise an exception if the parameter is missing) and another one for retrieving optional parameters (allows a default value to be specified if the parameter wasn't set).

Let's illustrate their usage with an example.

Suppose you have a method "makePayment" which takes three parameters specified by name. Its signature looks like this:

method: "makePayment"
named parameters:
	"recipient"    : string, mandatory
	"amount"       : number, mandatory
	"confirmation" : boolean, optional (defaults to false)

To extract the parameter values we need to create a new NamedParamsRetriever instance from the parsed request.

// Parse the request
JSONRPC2Request request = null;
		
try {
	// suppose "json" contains the request string JSON...
	request = JSONRPC2Request.parse(json);
			
} catch (JSONRPC2ParseException e) {
	// handle parse exception...
}

// Get the requested method...
String method = request.getMethod();

// ...after choosing the appropriate handler for this method...

// We expect named parameters (JSON object)
JSONRPC2ParamsType paramsType = request.getParamsType();
if (paramsType != JSONRPC2ParamsType.OBJECT) {
	// raise exception...
}

// Create named parameters instance from params Map
Map params = (Map)request.getParams();
NamedParamsRetriever np = new NamedParamsRetriever(params);

Now we are ready to retrieve the method parameters.

To retrieve mandatory parameters we must call a getXXX(String name) method where XXX is the expected value type. E.g. getDouble("amount") to retrieve a double value specified by the name "amount". The method will throw a JSONRPC2Error.INVALID_PARAMS exception if the parameter is missing.

To retrieve an optional parameter we must call a getOptXXX(String name, Object defaultValue) method where XXX is the expected value type. Use the second argument to specify a default value if the parameter wasn't set. E.g. getOptBoolean("confirmation", false) to retrieve an optional boolean parameter by the name "confirmation" with a default value of false.

// Extract parameters
try {
	// Extract mandatory "recipient" parameter
	String recipient = np.getString("recipient");

	// Extract mandatory "amount" parameter
	double amount = np.getDouble("amount");

	// Extract optional "confirmation" parameters, defaults to false
	// if undefined
	boolean confirmation = np.getOptBoolean("confirmation", false);

} catch (JSONRPC2Error e) {
	// If a mandatory parameter is missing or there is a type mismatch
	// you get an exception
}

The complete Java example can be downloaded from here.

9. API documentation

The Java docs for the package can be viewed here.

10. Change log

11. Download

The JSON-RPC 2.0 Base package is offered under a paid license. The license also gives you access to the source code if you need to modify or adapt the software to your particular application needs. Go to the download page to check the price and get the latest version.

You may also want to have a look at the sister package, a console client to query, test and debug JSON-RPC 2.0 services.