A reflection-free Dubbo consumer and provider for Spring Boot 3. Java keeps the business logic. A small Rust native data plane handles TCP connections, Dubbo framing, timeouts, heartbeats, backpressure, and provider I/O.
The public package contains the Java API and verified Windows, Linux, and Apple Silicon macOS native artifacts. Rust source is maintained in a separate private repository.
- Quick start
- Apple Silicon macOS
- Annotation package and 0.3 upgrade
- Choose a profile
- Critical runtime limits
- Production recipes
- Kubernetes without ZooKeeper
- Multiple provider applications
- Conditional references
- Supported contracts
- Configuration reference
- Safe tuning
- Troubleshooting
| You keep | This library replaces | Intentionally not included |
|---|---|---|
| Spring Boot, Java services, validation, repositories, DTOs | Official Dubbo runtime, Netty, Java Hessian, runtime proxies and reflection | ZooKeeper, Curator, metadata center, routers, generic invocation and callbacks |
Spring bean
-> generated typed client and Hessian codec
-> JNI
-> bounded Rust TCP data plane
-> Dubbo provider
-> generated Java dispatcher
-> your Java service
Use this library when provider addresses are static or available through Kubernetes Service DNS. Use official Dubbo when registry governance or the omitted Dubbo features are required.
- Java 21
- Spring Boot 3; version 3.2.4 is verified
- Maven 3.9 or newer
- Windows x64, Linux x64 with GLIBC 2.17 or newer, or Apple Silicon macOS 11 or newer
- A shared Java contract artifact used by both consumer and provider
Current release: 0.4.1.
Provider exceptions now keep the provider message and reported exception type instead of becoming a generic framework error. This works for Rust-to-Rust calls and for supported Apache Dubbo interoperability in both directions. Synchronous failures and failed CompletableFuture results use the same contract.
import com.reactor.rust.dubbo.runtime.DubboRemoteBusinessException;
try {
catalogService.findCustomer(customerId);
} catch (DubboRemoteBusinessException failure) {
log.warn("Provider call failed: type={}, message={}",
failure.remoteType(), failure.remoteMessage());
}Use typed result records and stable error codes for expected outcomes. Use exceptions for failed calls. Remote stack traces are not transferred; inspect provider logs and traces for the full stack. The Java annotation API, generated method signatures, success path, configuration keys, and native ABI 3 are unchanged.
GitHub Packages requires authentication even when the repository is public. Create a classic GitHub token with read:packages. Keep it outside the project.
Add the server to ~/.m2/settings.xml:
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
<servers>
<server>
<id>github</id>
<username>YOUR_GITHUB_USERNAME</username>
<password>${env.GITHUB_PACKAGES_TOKEN}</password>
</server>
</servers>
</settings>Set the token only in the current shell:
$env:GITHUB_PACKAGES_TOKEN='YOUR_CLASSIC_PAT'export GITHUB_PACKAGES_TOKEN='YOUR_CLASSIC_PAT'Add the repository, starter, code generator, one native platform artifact, and build enhancer to your application POM:
<properties>
<java-rust-dubbo.version>0.4.1</java-rust-dubbo.version>
</properties>
<repositories>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/esasmer-dou/java-rust-dubbo-spring-boot</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>github</id>
<url>https://maven.pkg.github.com/esasmer-dou/java-rust-dubbo-spring-boot</url>
</pluginRepository>
</pluginRepositories>
<dependencies>
<dependency>
<groupId>com.reactor</groupId>
<artifactId>java-rust-dubbo-spring-boot-starter</artifactId>
<version>${java-rust-dubbo.version}</version>
</dependency>
<dependency>
<groupId>com.reactor</groupId>
<artifactId>java-rust-dubbo-native-linux-x64</artifactId>
<version>${java-rust-dubbo.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.reactor</groupId>
<artifactId>java-rust-dubbo-codegen</artifactId>
<version>${java-rust-dubbo.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>21</release>
<parameters>true</parameters>
<annotationProcessorPaths>
<path>
<groupId>com.reactor</groupId>
<artifactId>java-rust-dubbo-codegen</artifactId>
<version>${java-rust-dubbo.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>com.reactor</groupId>
<artifactId>java-rust-dubbo-enhancer-maven-plugin</artifactId>
<version>${java-rust-dubbo.version}</version>
<executions>
<execution>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>Choose exactly one native artifact for the machine that runs the JVM:
| Runtime | Native artifact |
|---|---|
| Linux x64, GLIBC 2.17+ | java-rust-dubbo-native-linux-x64 |
| Windows x64 | java-rust-dubbo-native-windows-x64 |
| Apple Silicon macOS 11+ | java-rust-dubbo-native-macos-aarch64 |
Do not package multiple native artifacts in one deployment.
Use an ARM64 Java 21 JDK on M1, M2, M3, M4, or newer Apple Silicon hardware. Add this runtime dependency instead of the Linux or Windows artifact:
<dependency>
<groupId>com.reactor</groupId>
<artifactId>java-rust-dubbo-native-macos-aarch64</artifactId>
<version>${java-rust-dubbo.version}</version>
<scope>runtime</scope>
</dependency>Confirm that both the machine and JVM are ARM64:
uname -m
java -XshowSettings:properties -version 2>&1 | grep os.archExpected values are arm64 from uname and aarch64 or arm64 from Java. An x86_64 JVM is running through Rosetta and cannot load this artifact. Intel Macs are not supported by this module.
The dylib is built on a GitHub-hosted Apple Silicon runner with MACOSX_DEPLOYMENT_TARGET=11.0. CI verifies its ARM64 Mach-O architecture, macOS 11 minimum version, system-only dynamic dependencies, code signature, and JNI ABI before publication. This is a macOS desktop JVM library, not an iOS library. The runtime extracts it to ~/.java-rust-dubbo/native/<hash>/ and reuses it.
Place the interface and DTOs in a small shared JAR. Consumer and provider must use the same contract version and package names.
package com.example.store.api;
public record StoreView(long id, String code, String name) {}package com.example.store.api;
public interface StoreQueryService {
StoreView find(long id);
}Use the Reactor-owned annotations. They keep Apache and Rust classes unambiguous during a gradual migration:
package com.example.store.consumer;
import com.reactor.rust.dubbo.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@EnableDubbo
@SpringBootApplication
public class StoreConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(StoreConsumerApplication.class, args);
}
}package com.example.store.consumer;
import com.example.store.api.StoreQueryService;
import com.example.store.api.StoreView;
import com.reactor.rust.dubbo.annotation.DubboReference;
import org.springframework.stereotype.Service;
@Service
public final class StoreFacade {
@DubboReference(check = true, group = "store", version = "1.0")
private StoreQueryService storeQueryService;
public StoreView find(long id) {
return storeQueryService.find(id);
}
}@DubboReference fields must be instance fields, assignable, and typed as an interface. Do not mark them static or final. The Maven enhancer injects the generated client without runtime reflection.
Consumer configuration:
reactor.dubbo.profile=micro
reactor.dubbo.consumer.providers=127.0.0.1:20880
reactor.dubbo.consumer.timeout-ms=3000Business code remains a normal Java implementation:
package com.example.store.provider;
import com.example.store.api.StoreQueryService;
import com.example.store.api.StoreView;
import com.reactor.rust.dubbo.annotation.DubboService;
@DubboService(
interfaceClass = StoreQueryService.class,
group = "store",
version = "1.0",
executor = "store-query")
public final class StoreQueryServiceImpl implements StoreQueryService {
private final StoreRepository repository;
public StoreQueryServiceImpl(StoreRepository repository) {
this.repository = repository;
}
@Override
public StoreView find(long id) {
return repository.find(id);
}
}Provider configuration:
reactor.dubbo.profile=micro
reactor.dubbo.provider.enabled=true
reactor.dubbo.provider.port=20880
reactor.dubbo.provider.executors.store-query.max-concurrent=16The generator creates the Spring bean registration, typed dispatcher, method IDs, and Hessian codecs during the build. No runtime classpath scan or Java proxy is created.
mvn -U clean package
java -jar target/your-application.jarUse framework 0.3.1 or newer when the project is opened with NetBeans 17. Earlier code generators linked directly to a Java 21 compiler enum field that is absent from NetBeans 17's embedded parser model. Maven builds succeeded, but the IDE could mark valid @DubboReference classes as Error parsing file.
After upgrading the Maven property, reload the Maven project and run Clean and Build. No source-code or annotation change is required.
Version 0.3.0 uses one canonical package:
import com.reactor.rust.dubbo.annotation.DubboReference;
import com.reactor.rust.dubbo.annotation.DubboService;
import com.reactor.rust.dubbo.annotation.EnableDubbo;When upgrading from 0.2.x, change these imports and the Maven version. Controllers, business services, contracts, DTOs, route properties, and native settings do not change.
The starter does not bring classes under org.apache.dubbo.*. This is intentional. An existing official Apache provider can remain unchanged while a new Rust consumer uses the canonical Reactor annotation. Official Apache annotations are ignored by Reactor codegen in the default build.
If a large application cannot change old imports immediately, add the optional java-rust-dubbo-apache-compat-annotations dependency. Then set -Areactor.dubbo.apacheCompatibility=true in maven-compiler-plugin and <apacheCompatibility>true</apacheCompatibility> in the enhancer plugin. Treat this as a temporary migration mode.
Do not enable compatibility mode in an application that still contains real Apache Dubbo annotations. That mode deliberately processes the old package and cannot distinguish an old Reactor shim from a real Apache service. Use canonical Reactor imports on only the classes you migrate.
If Spring Boot Actuator is present, the library contributes rustDubboHealthIndicator. Check it through /actuator/health. The details include the selected profile, client readiness, provider readiness, and native metrics.
Start with micro. Change profile only after measuring RSS, p99 latency, rejection count, CPU throttling, and downstream pool wait together.
| Profile | Use it for | Trade-off |
|---|---|---|
micro |
Low-traffic or memory-first pods | Smallest worker, queue, connection, and buffer budgets |
balanced |
Steady mixed traffic | More concurrency and smoother bursts with moderate RSS |
throughput |
Load-tested high-volume services | Highest concurrency; larger queues and retained memory |
An explicit property always overrides the selected profile.
Profile defaults
| Setting | micro |
balanced |
throughput |
|---|---|---|---|
| Runtime I/O workers | 1 | 2 | 4 |
| Callback workers | 1 | 2 | 4 |
| Callback queue | 256 | 512 | 2048 |
| Native thread stack | 256 KiB | 256 KiB | 512 KiB |
| Connections per endpoint | 2 | 2 | 4 |
| Command queue per connection | 32 | 128 | 256 |
| Consumer max in-flight | 64 | 256 | 1024 |
| Retained request buffers | 16 | 32 | 64 |
| Largest retained buffer | 64 KiB | 128 KiB | 256 KiB |
| Provider I/O workers | 1 | 2 | 4 |
| Provider business workers | 4 | 8 | 16 |
| Provider queue | 64 | 256 | 1024 |
| Default provider concurrency | 16 | 128 | 512 |
The following values are the effective micro defaults unless an explicit property overrides them. Profile-dependent values grow in balanced and throughput.
| Limit | Default | What it protects |
|---|---|---|
| Consumer RPC deadline | 3000 ms |
Total time spent waiting for an in-flight permit, command queue, and response |
| Consumer startup wait | 3000 ms |
Time allowed for required providers to become reachable |
| TCP connect attempt | 3000 ms |
Internal limit for one native connection attempt; reconnect continues after failure |
| Provider request deadline | 30000 ms |
Provider queue, Java dispatch, and response completion time |
| Provider drain wait | 10000 ms |
Graceful shutdown wait for active work |
| Consumer payload | 8388608 bytes (8 MiB) |
One encoded request or response body |
| Provider payload | 8388608 bytes (8 MiB) |
One incoming request or generated response body |
| Decoded collection | 100000 items |
Object growth caused by a large List, Set, array, or Map |
| Connections per endpoint | 2 |
Persistent native connections and Kubernetes pod distribution |
| Consumer max in-flight | 64 |
Outstanding calls per generated client |
| Command queue | 32 per connection |
Calls waiting for a native connection |
| Callback queue | 256 |
Async completions waiting for a callback worker |
| Provider business workers | 4 |
Java business dispatch threads |
| Provider default concurrency | 16 |
Active calls sharing the default executor lane |
| Provider queue | 64 |
Work waiting for Java business workers |
| Retained request buffers | 16, at most 65536 bytes each |
Reusable direct-buffer retention after calls complete |
max-payload-bytes is a per-call hard ceiling. It is not reserved memory and it is not a total process memory limit.
- Consumer request buffers start at
1024bytes and grow only when encoding needs more space. - Provider response buffers also start at
1024bytes and grow only when needed. - Outbound requests are checked after Hessian2 encoding and before they enter the native command queue.
- Incoming frame length is checked before the complete body is allocated.
- Response growth stops at the configured provider limit.
- The limit applies to the encoded Dubbo body, including protocol and Hessian2 data. It may be slightly larger than the business DTO content.
- Consumer and provider limits should normally match. A provider limit may be lower when the service deliberately accepts smaller requests.
An 8 MiB limit does not allocate 8 MiB for every call. However, it permits a call to grow that large. Memory risk is therefore related to both payload and concurrency:
possible active payload memory ~= max-payload-bytes x active large calls
Do not keep an 8 MiB ceiling when valid payloads are below 256 KiB. Lowering the limit reduces the maximum burst allocation and rejects invalid data earlier.
reactor.dubbo.consumer.max-collection-items is a separate object-allocation guard. The generated provider dispatcher uses the same codec limit, even though the property is under consumer. A small encoded payload can still create many Java objects, so tune both byte and collection limits.
Example for small JSON-like DTO contracts:
reactor.dubbo.consumer.max-payload-bytes=1048576
reactor.dubbo.provider.max-payload-bytes=1048576
reactor.dubbo.consumer.max-collection-items=5000
reactor.dubbo.consumer.max-in-flight=32
reactor.dubbo.consumer.retained-buffers=8
reactor.dubbo.consumer.max-retained-buffer-bytes=65536There is currently no per-method payload override. Move unusually large contracts to a separate deployment or process when they require a very different memory budget.
The consumer timeout is one end-to-end RPC budget. Queue wait reduces the time left for the response. When the deadline expires, pending native state is removed and the invocation fails without automatic replay.
The provider timeout includes waiting in the provider business queue and waiting for the Java result. On expiry, Rust cancels the native response handle and returns a server-timeout response. Rust cannot safely interrupt a synchronous Java method that is blocked inside JDBC or an HTTP client. Those dependencies must have their own shorter timeouts.
A practical timeout chain leaves a small margin at every layer:
# Example when the inbound HTTP deadline is 3000 ms.
reactor.dubbo.provider.request-timeout-ms=2000
reactor.dubbo.consumer.timeout-ms=2500In this example, database and outbound HTTP timeouts should be below 2000 ms. Per-method timeout annotation values are not supported.
The native wire path does not currently compress payloads. There is no Gzip, LZ4, Zstd, or compression negotiation. Frames use the supported Hessian2 encoding directly.
This avoids compression CPU, temporary buffers, and p99 latency on normal API payloads. For large data, prefer a smaller DTO, pagination, or a separate bulk-transfer design. Application-managed compressed byte[] is possible, but the application must enforce a maximum decompressed size and accept the extra CPU and allocation cost.
Use this as a starting point for a small service with modest traffic and payloads below 1 MiB:
reactor.dubbo.profile=micro
reactor.dubbo.consumer.providers=store-provider:20880
reactor.dubbo.consumer.connections-per-endpoint=1
reactor.dubbo.consumer.max-in-flight=32
reactor.dubbo.consumer.command-queue-capacity=16
reactor.dubbo.consumer.retained-buffers=8
reactor.dubbo.consumer.max-payload-bytes=1048576Keep database writes inside the real DB pool capacity. A larger Dubbo queue does not create database capacity.
@DubboService(executor = "query")
final class QueryServiceImpl implements QueryService { /* business code */ }
@DubboService(executor = "command")
final class CommandServiceImpl implements CommandService { /* business code */ }reactor.dubbo.profile=balanced
reactor.dubbo.provider.enabled=true
reactor.dubbo.provider.business-workers=8
reactor.dubbo.provider.queue-capacity=64
reactor.dubbo.provider.executors.query.max-concurrent=8
reactor.dubbo.provider.executors.command.max-concurrent=2Raise only the required limit. Keep collection count and retained buffers bounded. Prefer pagination for large lists.
reactor.dubbo.consumer.max-payload-bytes=16777216
reactor.dubbo.consumer.max-collection-items=20000
reactor.dubbo.consumer.max-retained-buffer-bytes=65536Expose the provider through a normal Kubernetes Service:
apiVersion: v1
kind: Service
metadata:
name: store-provider
spec:
selector:
app: store-provider
ports:
- name: dubbo
port: 20880
targetPort: 20880Point the consumer to that Service DNS name:
env:
- name: REACTOR_DUBBO_PROFILE
value: "micro"
- name: REACTOR_DUBBO_CONSUMER_PROVIDERS
value: "store-provider.platform.svc.cluster.local:20880"Kubernetes balances new TCP connections. Existing persistent connections remain on the pod selected when they were opened. Increase connections-per-endpoint only when load tests show that more provider-pod distribution is needed. Use readiness probes, graceful shutdown, and a termination grace period longer than reactor.dubbo.provider.drain-timeout-ms.
Keep the Java fields unchanged. The exact interface + group + version identity decides which route is used:
@Service
public final class CheckoutService {
@DubboReference
private CustomerService customerService;
@DubboReference(group = "sales", version = "v2")
private OrderService orderService;
}Route each service to its own provider application:
reactor.dubbo.profile=micro
reactor.dubbo.consumer.require-explicit-routes=true
reactor.dubbo.consumer.routes.customer.interface-name=com.example.customer.CustomerService
reactor.dubbo.consumer.routes.customer.providers=customer-provider:20880
reactor.dubbo.consumer.routes.customer.connections-per-endpoint=2
reactor.dubbo.consumer.routes.customer.max-in-flight=32
reactor.dubbo.consumer.routes.customer.timeout-ms=750
reactor.dubbo.consumer.routes.order.interface-name=com.example.order.OrderService
reactor.dubbo.consumer.routes.order.group=sales
reactor.dubbo.consumer.routes.order.version=v2
reactor.dubbo.consumer.routes.order.providers=order-provider:20880
reactor.dubbo.consumer.routes.order.connections-per-endpoint=2
reactor.dubbo.consumer.routes.order.max-in-flight=16
reactor.dubbo.consumer.routes.order.timeout-ms=1500customer and order are readable labels only. Matching uses the full interface name, group, and version. Omitted per-route limits inherit the selected profile or global consumer value.
Use require-explicit-routes=true in production when references target different provider applications. A missing or duplicate exact route then stops startup. With the default false, an unmatched reference uses the backward-compatible global reactor.dubbo.consumer.providers value.
Route selection happens once while generated clients are created. It adds no reflection, registry query, proxy dispatch, or Java map lookup to each call. Routes have separate bounded connections and queues but share one small Rust Tokio runtime; no thread pool is created per route.
For Kubernetes, put the route map in application.yml or a mounted ConfigMap. Dynamic map names should not be split into individual environment variables. If one environment variable is required, use Spring Boot's standard SPRING_APPLICATION_JSON:
env:
- name: SPRING_APPLICATION_JSON
value: >-
{"reactor":{"dubbo":{"consumer":{"require-explicit-routes":true,
"routes":{"customer":{"interface-name":"com.example.customer.CustomerService",
"providers":"customer-provider.platform.svc.cluster.local:20880"},
"order":{"interface-name":"com.example.order.OrderService","group":"sales",
"version":"v2","providers":"order-provider.platform.svc.cluster.local:20880"}}}}}}You can keep an optional integration behind a normal Spring condition:
@Component
@ConditionalOnProperty(name = "vehicle.express.enabled", havingValue = "true")
public final class ExpressVehicleClient {
@DubboReference(group = "vehicle", version = "1.0")
private ExpressVehicleService service;
}With vehicle.express.enabled=false, Spring does not create the bean. The library therefore opens no native client, creates no socket, and does not require an explicit route for that reference.
With vehicle.express.enabled=true, the native client is created once during bean injection. Normal strict validation still applies. Add the exact route when require-explicit-routes=true:
reactor.dubbo.consumer.routes.express.interface-name=com.example.ExpressVehicleService
reactor.dubbo.consumer.routes.express.group=vehicle
reactor.dubbo.consumer.routes.express.version=1.0
reactor.dubbo.consumer.routes.express.providers=express-provider:20880This behavior also applies to beans controlled by Spring profiles. It does not add runtime reflection or per-call route lookup.
- Supported scalar types: Java primitives, boxed primitives,
String,BigDecimal,Date,LocalDate,LocalTime, andLocalDateTime. - Supported structures: enums, arrays,
byte[],List,Set,Collection,Map, nested records, and compatible Java beans. - Records are the simplest DTO choice. A bean must have readable properties and either a compatible builder or a no-argument constructor with writable properties.
- Synchronous return values,
void, andCompletableFuture<T>are supported. - Collection and payload limits are checked while decoding. Keep both limits close to valid business sizes.
| Parameter | Default | Meaning and recommended use |
|---|---|---|
interfaceClass |
void.class |
Type-safe service contract. Prefer interfaceClass = StoreQueryService.class in new code, especially when the implementation has multiple interfaces. |
interfaceName |
Empty | Fully qualified contract name as text. Use it only when source compatibility requires a string. Prefer interfaceClass; do not set both. |
group |
Empty | Logical namespace for services that share the same interface. The consumer value must match exactly. |
version |
Empty | Contract version used in service identity. The consumer value must match exactly. Deploy a new version when a contract change is not backward compatible. |
export |
true |
Registers the generated dispatcher with the native provider. With false, Spring can still create the implementation bean, but the service is not reachable over Dubbo. |
executor |
Empty | Names a bounded concurrency lane. All methods and services using the same name share that lane's active-call limit. An empty name uses the global default lane. |
executes |
-1 |
Positive value hard-codes the lane limit and overrides configuration. Prefer executor plus a property so each environment can be tuned without rebuilding. |
async |
false |
Accepted for source compatibility. Real non-blocking dispatch is determined by a CompletableFuture<T> method return type; async = true does not make a blocking method asynchronous. |
The provider service identity is the combination of interface name, group, and version. Consumer and provider must use the same three values.
If neither interfaceClass nor interfaceName is set, the implementation must implement exactly one interface. Explicit interfaceClass is clearer and safer for production code.
Named concurrency example:
@DubboService(
interfaceClass = StoreQueryService.class,
group = "store",
version = "1.0",
executor = "store-query")
public final class StoreQueryServiceImpl implements StoreQueryService {
// Business code stays in Java.
}reactor.dubbo.provider.executors.store-query.max-concurrent=16The value above limits the total active calls across every method sharing store-query; it does not create 16 threads per method. The global reactor.dubbo.provider.business-workers limit still applies. Reusing one executor name with conflicting executes values is rejected during provider startup.
Concurrency precedence is explicit:
@DubboService(executes = N)whenN > 0.reactor.dubbo.provider.executors.<executor>.max-concurrentfor a named executor.reactor.dubbo.provider.default-max-concurrentwhen no named value exists.
| Parameter | Default | Meaning and recommended use |
|---|---|---|
interfaceClass, interfaceName |
Inferred from field | Optional contract validation. The annotated field must already be typed as the service interface. |
group, version |
Empty | Must match the provider identity exactly. |
check |
true |
Includes this reference in startup readiness. Set false only when that provider is intentionally optional during startup. It does not enable retries. |
Unsupported values are not silently ignored. Explicit use fails the build.
| Parameter | Use instead |
|---|---|
timeout |
reactor.dubbo.consumer.timeout-ms or reactor.dubbo.provider.request-timeout-ms |
connections |
reactor.dubbo.consumer.connections-per-endpoint |
payload |
Consumer/provider max-payload-bytes properties |
retries |
Explicit application retry for idempotent operations only; native calls are not replayed automatically |
registry |
Global reactor.dubbo.consumer.providers or exact reactor.dubbo.consumer.routes.<name> entries with static addresses/Kubernetes Service DNS |
serialization |
The native protocol uses the supported Hessian2 subset |
protocol |
The native data plane uses classic dubbo:// |
path, actives, cluster, loadbalance |
No annotation equivalent in the bounded native runtime |
- The consumer keeps persistent bounded connections and reconnects after connection loss.
- Disconnected sockets are not selected. If all replicas are down, waiting remains bounded by the original RPC timeout.
startup-check=truewaits for required providers.@DubboReference(check = false)excludes only that reference from startup readiness.- Health details include
unreadyClients. Native metrics includeclientConnectionWaitsandclientConnectionWaitTimeouts. - Every RPC has a deadline. Set the RPC timeout below the inbound HTTP timeout.
- Queues and in-flight calls are bounded. Under overload, rejection is safer than unbounded RSS and tail latency growth.
- Automatic business retries are not provided. Retry only idempotent operations and keep retries within the caller deadline.
- Provider shutdown stops accepting work and waits up to the configured drain timeout.
Spring Boot relaxed binding applies. These three forms set the same value:
reactor.dubbo.consumer.max-in-flight=64-Dreactor.dubbo.consumer.max-in-flight=64
- name: REACTOR_DUBBO_CONSUMER_MAX_IN_FLIGHT
value: "64"The three forms above apply to scalar properties. Use YAML/properties, a mounted ConfigMap, or SPRING_APPLICATION_JSON for the named route map.
All runtime and consumer properties
| Property | micro default |
Purpose |
|---|---|---|
reactor.dubbo.enabled |
true |
Starts or disables the generated Dubbo runtime |
reactor.dubbo.profile |
micro |
Selects the starting resource preset |
reactor.dubbo.runtime.io-workers |
1 |
Shared native consumer I/O workers |
reactor.dubbo.runtime.callback-workers |
1 |
Async completion workers |
reactor.dubbo.runtime.callback-queue-capacity |
256 |
Bounded async completion queue |
reactor.dubbo.runtime.thread-stack-bytes |
262144 |
Stack per native runtime thread |
reactor.dubbo.consumer.providers |
127.0.0.1:20880 |
Comma-separated host:port endpoints |
reactor.dubbo.consumer.connections-per-endpoint |
2 |
Persistent connections per endpoint |
reactor.dubbo.consumer.command-queue-capacity |
32 |
Waiting calls per connection |
reactor.dubbo.consumer.max-in-flight |
64 |
Outstanding calls per generated client |
reactor.dubbo.consumer.heartbeat-interval-ms |
30000 |
Idle connection heartbeat; 0 disables it |
reactor.dubbo.consumer.timeout-ms |
3000 |
RPC deadline |
reactor.dubbo.consumer.max-payload-bytes |
8388608 |
Request/response hard limit |
reactor.dubbo.consumer.max-collection-items |
100000 |
Decoded collection item limit |
reactor.dubbo.consumer.initial-buffer-bytes |
1024 |
Initial request buffer size |
reactor.dubbo.consumer.retained-buffers |
16 |
Reusable request buffers; 0 minimizes retention |
reactor.dubbo.consumer.max-retained-buffer-bytes |
65536 |
Largest buffer kept for reuse |
reactor.dubbo.consumer.startup-check |
true |
Waits for required providers at startup |
reactor.dubbo.consumer.startup-timeout-ms |
3000 |
Maximum startup readiness wait |
reactor.dubbo.consumer.require-explicit-routes |
false |
Requires an exact named route for every generated reference |
reactor.dubbo.consumer.routes.<name>.interface-name |
None | Fully qualified interface in the exact route key |
reactor.dubbo.consumer.routes.<name>.group |
Empty | Group matching @DubboReference |
reactor.dubbo.consumer.routes.<name>.version |
Empty | Version matching @DubboReference |
reactor.dubbo.consumer.routes.<name>.providers |
None | Service-specific endpoint list |
reactor.dubbo.consumer.routes.<name>.<limit> |
Inherited | Per-route connection, queue, in-flight, timeout, payload, collection, and buffer limits |
All provider properties
| Property | micro default |
Purpose |
|---|---|---|
reactor.dubbo.provider.enabled |
false |
Starts the native provider listener |
reactor.dubbo.provider.port |
20880 |
Dubbo TCP port |
reactor.dubbo.provider.io-workers |
1 |
Dedicated provider I/O workers |
reactor.dubbo.provider.business-workers |
4 |
Maximum Java dispatch workers |
reactor.dubbo.provider.queue-capacity |
64 |
Bounded waiting provider work |
reactor.dubbo.provider.max-payload-bytes |
8388608 |
Provider payload hard limit |
reactor.dubbo.provider.request-timeout-ms |
30000 |
Provider execution deadline |
reactor.dubbo.provider.drain-timeout-ms |
10000 |
Graceful shutdown wait |
reactor.dubbo.provider.default-max-concurrent |
16 |
Fallback method concurrency |
reactor.dubbo.provider.executors.<name>.max-concurrent |
16 |
Named workload-lane concurrency |
@DubboService(executes = N) has the highest priority. Otherwise, executor = "name" uses the matching named property. If neither is present, the global default is used.
- Measure provider execution time, DB pool wait, CPU throttling, p99, rejection count, and RSS.
- Fix slow business code and database access first.
- Match provider executor limits to downstream capacity.
- Match consumer
max-in-flightto work the provider can finish before timeout. - Add connections only when socket saturation or Kubernetes distribution is proven.
- Increase queues last. Queues store waiting work; they do not add capacity.
| Term | Meaning |
|---|---|
| RSS | Physical memory currently attributed to the process or container |
| p99 | Latency below which 99% of measured requests complete |
| In-flight | A request that started but has not completed yet |
| Backpressure | Rejecting or slowing new work when bounded capacity is full |
| Queue | Work waiting for an available worker or connection |
| Heartbeat | Small periodic message used to keep and verify an idle connection |
| Idempotent | Safe to repeat without applying the business effect twice |
| Symptom | Check |
|---|---|
Maven returns 401 |
Token exists, has read:packages, and server ID is exactly github |
Maven returns 403 or 404 |
Token owner can access the repository and package |
| Native library cannot load | Exactly one native artifact matching Windows x64, Linux x64, or macOS ARM64 is present |
| macOS reports an unsupported platform | Use an ARM64 Java 21 JDK; an x86_64 JDK runs through Rosetta and is not compatible |
| Startup reports provider unavailable | Provider address, port, readiness, and startup timeout are correct |
| Build rejects an annotation option | Use only the supported annotation subset listed above |
| Generated client or injection is missing | Annotation processor and enhancer plugin both ran during mvn package |
| DTO decode fails | Consumer and provider use the same contract package, class names, and compatible fields |
| p99 and RSS rise under load | Inspect DB/provider capacity before increasing workers, connections, or queues |
| Traffic reaches too few provider pods | Measure and then raise connections-per-endpoint gradually |
Release assets include Windows DLL, Linux SO, Apple Silicon macOS dylib, SHA-256 checksums, and CycloneDX SBOMs. NATIVE_SHA256SUMS pins the distributed artifacts. Third-party native components are listed in THIRD_PARTY_NOTICES and the SBOM files.
The public Java source is licensed under Apache License 2.0. See Releases for published packages and native assets.