==== 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

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
[source,java,role="primary"]
----
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));
  }
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
import io.jooby.rpc.jsonrpc.JsonRpc

@JsonRpc("movies")
class MovieService {

  fun getById(id: Int): Movie {
    return database.asSequence()
      .filter { it.id == id }
      .firstOrNull() ?: throw 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
[source,java,role="primary"]
----
import io.jooby.Jooby;
import io.jooby.jackson.JacksonModule;

public class App extends Jooby {
  {
    install(new JacksonModule());      // <1>

    jsonrpc(new MovieServiceRpc_());   // <2>

    // Alternatively, you can override the default path:
    // jsonrpc("/json-rpc", new MovieServiceRpc_());
  }
}
----

.Kotlin
[source,kotlin,role="secondary"]
----
import io.jooby.kt.Kooby
import io.jooby.jackson.JacksonModule

class App : Kooby({
  
  install(JacksonModule())             // <1>

  jsonrpc(MovieServiceRpc_())          // <2>

  // Custom endpoint:
  // jsonrpc("/json-rpc", MovieServiceRpc_())
})
----

1. Install a JSON engine
2. 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** (`JacksonModule`)
* **Jackson 3** (`Jackson3Module`)
* **Avaje JSON-B** (`AvajeJsonbModule`)

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:

[source,json]
----
{
  "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.
