You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Jooby provides full support for the JSON-RPC 2.0 specification, allowing you to build robust RPC APIs using standard Java and Kotlin controllers.
4
+
5
+
The implementation leverages Jooby's Annotation Processing Tool (APT) to generate highly optimized, reflection-free dispatchers at compile time.
6
+
7
+
===== Usage
8
+
9
+
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.
10
+
11
+
.JSON-RPC
12
+
[source,java,role="primary"]
13
+
----
14
+
import io.jooby.jsonrpc.JsonRpc;
15
+
16
+
@JsonRpc("movies")
17
+
public class MovieService {
18
+
19
+
public Movie getById(int id) {
20
+
return database.stream()
21
+
.filter(m -> m.id() == id)
22
+
.findFirst()
23
+
.orElseThrow(() -> new NotFoundException("Movie not found: " + id));
24
+
}
25
+
}
26
+
----
27
+
28
+
.Kotlin
29
+
[source,kotlin,role="secondary"]
30
+
----
31
+
import io.jooby.jsonrpc.JsonRpc
32
+
33
+
@JsonRpc("movies")
34
+
class MovieService {
35
+
36
+
fun getById(id: Int): Movie {
37
+
return database.asSequence()
38
+
.filter { it.id == id }
39
+
.firstOrNull() ?: throw NotFoundException("Movie not found: $id")
40
+
}
41
+
}
42
+
----
43
+
44
+
When the Jooby APT detects the `@JsonRpc` annotation, it generates a class ending in `Rpc_` (e.g., `MovieServiceRpc_`) that implements the `io.jooby.jsonrpc.JsonRpcService` interface.
45
+
46
+
The annotation dictates how the protocol methods are named and exposed:
47
+
48
+
* **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.
49
+
* **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.
50
+
51
+
**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.
52
+
53
+
===== Registration
54
+
55
+
Register the generated `JsonRpcService` in your application using the `jsonrpc` method. You must also install a supported JSON engine.
56
+
57
+
.JSON-RPC
58
+
[source,java,role="primary"]
59
+
----
60
+
import io.jooby.Jooby;
61
+
import io.jooby.jackson.JacksonModule;
62
+
63
+
public class App extends Jooby {
64
+
{
65
+
install(new JacksonModule()); // <1>
66
+
67
+
jsonrpc(new MovieServiceRpc_()); // <2>
68
+
69
+
// Alternatively, you can override the default path:
70
+
// jsonrpc("/json-rpc", new MovieServiceRpc_());
71
+
}
72
+
}
73
+
----
74
+
75
+
.Kotlin
76
+
[source,kotlin,role="secondary"]
77
+
----
78
+
import io.jooby.kt.Kooby
79
+
import io.jooby.jackson.JacksonModule
80
+
81
+
class App : Kooby({
82
+
83
+
install(JacksonModule()) // <1>
84
+
85
+
jsonrpc(MovieServiceRpc_()) // <2>
86
+
87
+
// Custom endpoint:
88
+
// jsonrpc("/json-rpc", MovieServiceRpc_())
89
+
})
90
+
----
91
+
92
+
1. Install a JSON engine
93
+
2. Register the generated JSON-RPC service
94
+
95
+
===== JSON Engine Support
96
+
97
+
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).
98
+
99
+
Supported engines include:
100
+
101
+
* **Jackson 2** (`JacksonModule`)
102
+
* **Jackson 3** (`Jackson3Module`)
103
+
* **Avaje JSON-B** (`AvajeJsonbModule`)
104
+
105
+
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.
106
+
107
+
===== Error Mapping
108
+
109
+
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.
110
+
111
+
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:
112
+
113
+
[source,json]
114
+
----
115
+
{
116
+
"jsonrpc": "2.0",
117
+
"error": {
118
+
"code": -32004,
119
+
"message": "Not found",
120
+
"data": "Movie not found: 99"
121
+
},
122
+
"id": 1
123
+
}
124
+
----
125
+
126
+
====== Standard Protocol Errors
127
+
128
+
The engine handles core JSON-RPC 2.0 protocol errors automatically, returning HTTP 200 OK with the corresponding error payload:
129
+
130
+
* `-32700`: Parse error (Malformed JSON)
131
+
* `-32600`: Invalid Request (Missing version, ID, or malformed envelope)
132
+
* `-32601`: Method not found
133
+
* `-32602`: Invalid params (Missing arguments, type mismatches)
134
+
* `-32603`: Internal error
135
+
136
+
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`).
137
+
138
+
===== Batch Processing
139
+
140
+
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.
141
+
142
+
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.
Copy file name to clipboardExpand all lines: docs/asciidoc/tRPC.adoc
+90-22Lines changed: 90 additions & 22 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,16 +1,17 @@
1
-
=== tRPC
1
+
====tRPC
2
2
3
3
The tRPC module provides end-to-end type safety by integrating the https://trpc.io/[tRPC] protocol directly into Jooby.
4
4
5
5
Because the `io.jooby.trpc` package is included in Jooby core, there are no extra dependencies to add to your project. This integration allows you to write standard Java/Kotlin controllers and consume them directly in the browser using the official `@trpc/client`—complete with 100% type safety, autocomplete, and zero manual client generation.
6
6
7
-
==== Usage
7
+
=====Usage
8
8
9
9
Because tRPC relies heavily on JSON serialization to communicate with the frontend client, a JSON module **must** be installed prior to the `TrpcModule`.
10
10
11
-
NOTE: Currently, Jooby only provides the required `TrpcParser` SPI implementation for two JSON engines: **Jackson 2/3** and **AvajeJsonbModule**. Using other JSON modules (like Gson) will result in a missing service exception at startup.
11
+
NOTE: Currently, Jooby only provides the required `TrpcParser` SPI implementation for two JSON engines: **Jackson 2/3** and **AvajeJsonbModule**. Using other JSON modules (like Gson) will result in a missing service exception at startup.
12
12
13
-
[source, java]
13
+
.Java
14
+
[source,java,role="primary"]
14
15
----
15
16
import io.jooby.Jooby;
16
17
import io.jooby.json.JacksonModule;
@@ -27,19 +28,35 @@ public class App extends Jooby {
27
28
}
28
29
----
29
30
31
+
.Kotlin
32
+
[source,kotlin,role="secondary"]
33
+
----
34
+
import io.jooby.kt.Kooby
35
+
import io.jooby.json.JacksonModule
36
+
import io.jooby.trpc.TrpcModule
37
+
38
+
class App : Kooby({
39
+
install(JacksonModule()) // <1>
40
+
41
+
install(TrpcModule()) // <2>
42
+
43
+
install(MovieService_()) // <3>
44
+
})
45
+
----
46
+
30
47
1. Install a supported JSON engine (Jackson or Avaje)
31
48
2. Install the tRPC extension
32
49
3. Register your @Trpc annotated controllers (using the APT generated route)
33
50
34
-
==== Writing a Service
51
+
===== Writing a Service
35
52
36
53
You can define your procedures using explicit tRPC annotations or a hybrid approach combining tRPC with standard HTTP methods:
37
54
38
55
* **Explicit Annotations:** Use `@Trpc.Query` (maps to `GET`) and `@Trpc.Mutation` (maps to `POST`).
39
56
* **Hybrid Annotations:** Combine the base `@Trpc` annotation with Jooby's standard HTTP annotations. A `@GET` resolves to a tRPC query, while state-changing methods (`@POST`, `@PUT`, `@DELETE`) resolve to tRPC mutations.
40
57
41
-
.MovieService
42
-
[source,java]
58
+
.Java
59
+
[source,java,role="primary"]
43
60
----
44
61
import io.jooby.annotation.Trpc;
45
62
import io.jooby.annotation.DELETE;
@@ -71,12 +88,45 @@ public class MovieService {
71
88
}
72
89
----
73
90
74
-
==== Build Tool Configuration
91
+
.Kotlin
92
+
[source,kotlin,role="secondary"]
93
+
----
94
+
import io.jooby.annotation.Trpc
95
+
import io.jooby.annotation.DELETE
96
+
97
+
data class Movie(val id: Int, val title: String, val year: Int)
98
+
99
+
@Trpc("movies") // Defines the 'movies' namespace
100
+
class MovieService {
101
+
102
+
// 1. Explicit tRPC Query
103
+
@Trpc.Query
104
+
fun getById(id: Int): Movie {
105
+
return Movie(id, "Pulp Fiction", 1994)
106
+
}
107
+
108
+
// 2. Explicit tRPC Mutation
109
+
@Trpc.Mutation
110
+
fun create(movie: Movie): Movie {
111
+
// Save to database logic here
112
+
return movie
113
+
}
114
+
115
+
// 3. Hybrid Mutation
116
+
@Trpc
117
+
@DELETE
118
+
fun delete(id: Int) {
119
+
// Delete from database
120
+
}
121
+
}
122
+
----
123
+
124
+
===== Build Configuration
75
125
76
126
To generate the `trpc.d.ts` TypeScript definitions, you must configure the Jooby build plugin for your project. The generator parses your source code and emits the definitions during the compilation phase.
Once the project is compiled, the build plugin generates a `trpc.d.ts` file containing your exact `AppRouter` shape. You can then use the official client in your TypeScript frontend:
115
165
116
-
[source,bash]
166
+
[source,bash]
117
167
----
118
168
npm install @trpc/client
119
169
----
120
170
121
-
[source,typescript]
171
+
[source,typescript]
122
172
----
123
173
import { createTRPCProxyClient, httpLink } from '@trpc/client';
124
174
import type { AppRouter } from './target/classes/trpc'; // Path to generated file
@@ -137,14 +187,15 @@ const movie = await trpc.movies.getById.query(1);
The tRPC protocol expects specific JSON-RPC error codes (e.g., `-32600` for Bad Request). `TrpcModule` automatically registers a specialized error handler to format these errors.
144
194
145
195
If you throw custom domain exceptions, you can map them directly to tRPC error codes using the service registry so the frontend client receives the correct error state:
Sometimes you have custom Java types (like `java.util.UUID` or `java.math.BigDecimal`) that you want translated into specific TypeScript primitives. You can define these overrides in your build tool:
163
231
164
-
**Maven:**
165
-
[source,xml]
232
+
.Maven
233
+
[source,xml,role="primary"]
166
234
----
167
235
<configuration>
168
236
<customTypeMappings>
@@ -172,8 +240,8 @@ Sometimes you have custom Java types (like `java.util.UUID` or `java.math.BigDec
0 commit comments