This guide covers production-style Spring Boot usage, including registration strategies, conflict handling, and runtime customization.
Replace latest-version with the release you want to use.
Maven:
<properties>
<jsonrpc.version>latest-version</jsonrpc.version>
</properties>
<dependency>
<groupId>io.github.limehee</groupId>
<artifactId>jsonrpc-spring-boot-starter</artifactId>
<version>${jsonrpc.version}</version>
</dependency>Gradle (Kotlin DSL):
val jsonrpcVersion = "latest-version"
dependencies {
implementation("io.github.limehee:jsonrpc-spring-boot-starter:$jsonrpcVersion")
}Gradle (Groovy DSL):
def jsonrpcVersion = "latest-version"
dependencies {
implementation "io.github.limehee:jsonrpc-spring-boot-starter:${jsonrpcVersion}"
}Gradle Version Catalog (libs.versions.toml):
[versions]
jsonrpc = "latest-version"
[libraries]
jsonrpc-spring-boot-starter = { module = "io.github.limehee:jsonrpc-spring-boot-starter", version.ref = "jsonrpc" }dependencies {
implementation(libs.jsonrpc.spring.boot.starter)
}When jsonrpc.enabled=true (default), the starter auto-registers a WebMVC endpoint:
POST ${jsonrpc.path}- default path:
/jsonrpc - content type:
application/json
Example:
jsonrpc:
enabled: true
path: /jsonrpcimport com.limehee.jsonrpc.core.JsonRpcMethod;
import com.limehee.jsonrpc.core.JsonRpcParam;
import org.springframework.stereotype.Service;
@Service
class MathRpcService {
@JsonRpcMethod("math.sum")
public int sum(@JsonRpcParam("left") int left, @JsonRpcParam("right") int right) {
return left + right;
}
@JsonRpcMethod
public String ping() {
return "pong";
}
}Name rule:
@JsonRpcMethod("math.sum")-> explicit method name@JsonRpcMethod(empty value) -> Java method name (ping)
import tools.jackson.databind.node.StringNode;
import com.limehee.jsonrpc.core.JsonRpcMethodRegistration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class ManualRpcConfig {
@Bean
JsonRpcMethodRegistration manualPingRegistration() {
return JsonRpcMethodRegistration.of("manual.ping", params -> StringNode.valueOf("pong-manual"));
}
}Use this when you need deterministic explicit registration without method scanning.
import com.limehee.jsonrpc.core.JsonRpcMethodRegistration;
import com.limehee.jsonrpc.core.JsonRpcTypedMethodHandlerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class TypedRpcConfig {
record UpperIn(String value) {
}
record UpperOut(String value) {
}
@Bean
JsonRpcMethodRegistration typedUpperRegistration(JsonRpcTypedMethodHandlerFactory factory) {
return JsonRpcMethodRegistration.of(
"typed.upper",
factory.unary(UpperIn.class, in -> new UpperOut(in.value().toUpperCase()))
);
}
}Use this when you want compile-time DTO types and reuse the standard binder/writer pipeline.
Runnable sample code:
../samples/spring-boot-demo/src/main/java/com/limehee/jsonrpc/sample/GreetingRpcService.java../samples/spring-boot-demo/src/main/java/com/limehee/jsonrpc/sample/SampleRegistrationConfig.java../samples/spring-boot-demo/src/main/java/com/limehee/jsonrpc/sample/OutboundRequestCompositionExample.java
Two registration phases exist in auto-configuration:
JsonRpcMethodRegistrationbeans are applied while creatingJsonRpcDispatcher.@JsonRpcMethodscanner (JsonRpcAnnotatedMethodRegistrar) runs after singleton initialization and registers annotated handlers.
Within manual registrations, orderedStream() is used, so @Order / Ordered can control order.
Duplicate method names are governed by jsonrpc.method-registration-conflict-policy:
REJECT(default): throws on duplicate registration.REPLACE: later registration replaces earlier registration.
Implications:
- With
REPLACE, annotation phase can override previously registered manual handlers with the same name. - With
REJECT, duplication between manual and annotation styles fails fast.
Configuration:
jsonrpc:
method-registration-conflict-policy: REJECTparams is mapped as a whole to the single declared parameter type.
@JsonRpcMethod("greet")
public String greet(GreetParams params) {
return "hello " + params.name();
}
record GreetParams(String name) {
}Binding mode is selected by request params shape:
paramsis object -> named binding- otherwise -> positional array binding
Named binding name resolution order:
@JsonRpcParam("...")- Java reflection parameter name (
-parametersrequired; already enabled in this project)
Example:
@JsonRpcMethod("sum")
public int sum(@JsonRpcParam("left") int left, @JsonRpcParam("right") int right) {
return left + right;
}Request:
{
"jsonrpc": "2.0",
"method": "sum",
"params": {
"left": 1,
"right": 2
},
"id": 1
}Positional example:
@JsonRpcMethod("sum")
public int sum(int left, int right) {
return left + right;
}Request:
{
"jsonrpc": "2.0",
"method": "sum",
"params": [
1,
2
],
"id": 1
}Return values are serialized through JsonRpcResultWriter (default uses Jackson valueToTree).
Supported practical types include:
- primitives/wrappers
- records/POJOs
Map,List, collection typesJsonNode
Default auto-configuration uses DefaultJsonRpcRequestValidator with
JsonRpcParamsTypeViolationCodePolicy.INVALID_PARAMS, so request params with non-object/non-array shape
returns -32602.
Use configuration to change this behavior:
jsonrpc:
validation:
request:
require-id-member: false
allow-fractional-id: false
reject-response-fields: true
params-type-violation-code-policy: INVALID_REQUESTIf you need fully custom validation logic, you can still override the validator bean.
JsonRpcResponseValidationOptions is also configurable through properties:
jsonrpc:
validation:
response:
require-json-rpc-version-20: true
require-id-member: true
allow-null-id: true
allow-string-id: true
allow-numeric-id: true
allow-fractional-id: true
require-exclusive-result-or-error: true
require-error-object-when-present: true
require-integer-error-code: true
require-string-error-message: true
reject-request-fields: false
error-code:
policy: ANY_INTEGER
range:
min: null
max: nullYou can override only the options you need. Example:
jsonrpc:
validation:
response:
allow-fractional-id: false
reject-request-fields: true
error-code:
policy: STANDARD_OR_SERVER_ERROR_RANGEAuto-configuration exposes both JsonRpcResponseValidationOptions and
JsonRpcResponseValidator beans, and also a JsonRpcResponseParser bean configured with
jsonrpc.validation.response.reject-duplicate-members. These components are intended for client/bidirectional
integrations and are not part of the default HTTP request dispatch path.
Disable annotation scanning when you only want explicit registrations:
jsonrpc:
scan-annotated-methods: falseProperties:
jsonrpc:
method-allowlist: [ math.sum, ping ]
method-denylist: [ admin.reset ]Rules:
- Empty allowlist means all methods are allowed unless denied.
- Non-empty allowlist means only listed methods are allowed.
- Denylist always wins over allowlist.
rpc.*methods are blocked by JSON-RPC request validation and by the default registry regardless of allow/deny lists.
Default is direct execution in the request thread.
Enable executor mode:
jsonrpc:
notification-executor-enabled: true
notification-executor-bean-name: applicationTaskExecutorResolution order in executor mode:
- Explicit
notification-executor-bean-name - Single
Executorbean in context applicationTaskExecutor- Fallback to direct executor
If the configured bean name is missing, startup fails with an explicit error.
If Micrometer MeterRegistry exists and jsonrpc.metrics-enabled=true (default), metrics interceptor/observer are
enabled.
Key metrics:
jsonrpc.server.callsjsonrpc.server.latencyjsonrpc.server.stage.eventsjsonrpc.server.failuresjsonrpc.server.transport.errorsjsonrpc.server.batch.*jsonrpc.server.notification.*
Configuration:
jsonrpc:
metrics-enabled: true
metrics-latency-histogram-enabled: true
metrics-latency-percentiles: [ 0.9, 0.95, 0.99 ]
metrics-max-method-tag-values: 100Auto-configuration uses @ConditionalOnMissingBean, so you can replace any component by defining your own bean.
Common override points:
JsonRpcRequestParserJsonRpcRequestValidatorJsonRpcMethodRegistryJsonRpcMethodInvokerJsonRpcExceptionResolverJsonRpcResponseComposerJsonRpcNotificationExecutorJsonRpcHttpStatusStrategy
Response-side protocol components are also available in jsonrpc-core:
JsonRpcEnvelopeClassifierJsonRpcResponseParserJsonRpcResponseValidatorJsonRpcResponseValidationOptions
Auto-configuration provides these as beans for reuse in custom bidirectional transports. They are not used by the default HTTP request dispatcher.
Example HTTP status strategy:
import com.limehee.jsonrpc.core.JsonRpcResponse;
import com.limehee.jsonrpc.spring.webmvc.JsonRpcHttpStatusStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import java.util.List;
@Configuration
class RpcHttpConfig {
@Bean
JsonRpcHttpStatusStrategy jsonRpcHttpStatusStrategy() {
return new JsonRpcHttpStatusStrategy() {
public HttpStatus statusForSingle(JsonRpcResponse response) {
return HttpStatus.OK;
}
public HttpStatus statusForBatch(List<JsonRpcResponse> responses) {
return HttpStatus.OK;
}
public HttpStatus statusForNotificationOnly() {
return HttpStatus.NO_CONTENT;
}
public HttpStatus statusForParseError() {
return HttpStatus.BAD_REQUEST;
}
public HttpStatus statusForRequestTooLarge() {
return HttpStatus.PAYLOAD_TOO_LARGE;
}
};
}
}- Registration and binding deep dive:
registration-and-binding.md - Full property table and validation rules:
configuration-reference.md - Extension design:
extension-points.md