Jooby

JSON-RPC

Jooby provides full support for the JSON-RPC 2.0 specification, allowing you to build robust RPC APIs using standard Java and Kotlin controllers.

The implementation leverages Jooby’s Annotation Processing Tool (APT) to generate highly optimized, reflection-free dispatchers at compile time.

Usage

Add dependencies:

Maven
Gradle
<!-- Jackson module-->
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-jackson3</artifactId>
  <version>4.3.0</version>
</dependency>

<!-- Jackson JSON-RPC implementation-->
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-jsonrpc-jackson3</artifactId>
  <version>4.3.0</version>
</dependency>

<!-- JSON-RPC Module-->
<dependency>
  <groupId>io.jooby</groupId>
  <artifactId>jooby-jsonrpc</artifactId>
  <version>4.3.0</version>
</dependency>

To expose a JSON-RPC endpoint, annotate your controller or service with @JsonRpc. You can optionally provide a namespace to the annotation, which will prefix all generated method names for that class.

JSON-RPC
Java
Kotlin
import io.jooby.rpc.jsonrpc.JsonRpc;

@JsonRpc("movies")
public class MovieService {

  public Movie getById(int id) {
    return database.stream()
      .filter(m -> m.id() == id)
      .findFirst()
      .orElseThrow(() -> new NotFoundException("Movie not found: " + id));
  }
}

When the Jooby APT detects the @JsonRpc annotation, it generates a class ending in Rpc_ (e.g., MovieServiceRpc_) that implements the io.jooby.rpc.jsonrpc.JsonRpcService interface.

The annotation dictates how the protocol methods are named and exposed:

  • Class Level: Placing @JsonRpc on a class treats its public methods as JSON-RPC endpoints. An optional string value defines a namespace prefix for all methods within that class (e.g., @JsonRpc("movies")). If no value is provided, no namespace is applied.

  • Method Level: By default, the generated JSON-RPC method name is exactly the name of the Java/Kotlin method, prefixed by the class-level namespace if one is present (e.g., movies.getById or just getById if no namespace was set). You can also place @JsonRpc("customName") directly on specific methods to explicitly override this default naming convention.

Mixing Annotations: You can freely mix standard REST annotations (like @GET, @POST) and @JsonRpc on the same class. The APT handles this by generating two entirely separate dispatchers: one standard MVC extension (e.g., MovieService_) and one JSON-RPC service (e.g., MovieServiceRpc_). They do not interfere with each other, allowing you to expose the exact same business logic over both REST and JSON-RPC simultaneously.

Registration

Register the generated JsonRpcService in your application using the jsonrpc method. You must also install a supported JSON engine.

JSON-RPC
Java
Kotlin
import io.jooby.Jooby;
import io.jooby.jackson.Jackson3Module;

{
  install(new Jackson3Module());                      // (1)

  install(new JsonRpcJackson3Module());               // (2)

  install(new JsonRpcModule(new MovieServiceRpc_())); // (3)
}
  1. Install a JSON engine

  2. Install a JSON-RPC implementation

  3. Register the generated JSON-RPC service

JSON Engine Support

The JSON-RPC extension delegates payload parsing and serialization to Jooby’s standard JSON modules while enforcing strict JSON-RPC 2.0 compliance (such as the mutual exclusivity of result and error fields).

Supported engines include:

  • Jackson 2: Jackson2Module, JsonRpcJackson2Module

  • Jackson 3: Jackson3Module, JsonRpcJackson3Module

  • Avaje JSON-B: AvajeJsonbModule, JsonRpcAvajeJsonbModule

No additional configuration is required. The generated dispatcher automatically hooks into the installed engine using the JsonRpcParser and JsonRpcDecoder interfaces, ensuring primitive types are strictly validated and parsed.

Error Mapping

Jooby seamlessly bridges standard Java application exceptions and HTTP status codes into the JSON-RPC 2.0 format using the JsonRpcErrorCode mapping. You do not need to throw custom protocol exceptions for standard failures.

When an application exception is thrown (like a NotFoundException with an HTTP 404 status), the dispatcher catches it and translates it into a compliant JSON-RPC error. The standard HTTP status defines the error code, while the specific exception message is safely passed into the data field:

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32004,
    "message": "Not found",
    "data": "Movie not found: 99"
  },
  "id": 1
}

Standard Protocol Errors

The engine handles core JSON-RPC 2.0 protocol errors automatically, returning HTTP 200 OK with the corresponding error payload:

  • -32700: Parse error (Malformed JSON)

  • -32600: Invalid Request (Missing version, ID, or malformed envelope)

  • -32601: Method not found

  • -32602: Invalid params (Missing arguments, type mismatches)

  • -32603: Internal error

Application-defined errors map standard HTTP status codes to the -32000 to -32099 range (e.g., an HTTP 404 maps to -32004, an HTTP 401 maps to -32001).

Batch Processing

Batch processing is natively supported. Clients can send an array of JSON-RPC request objects, and the dispatcher will process them and return an array of corresponding response objects.

In accordance with the specification, notifications (requests lacking an id field) are processed normally but generate no response payload, leaving no trace in the returned batch array.