Back-end business services use Java + Dubbo, and management background, task programs or testing tools use .NET. Such technology combinations are not uncommon. When the two parties need to collaborate, we hope that the C# application can directly call the existing Dubbo interface instead of maintaining another layer of HTTP adaptation code for each interface.
DubboNet is the .NET Dubbo client library provided for this scenario. It can discover services, read method signatures, select Providers, and then complete the call through Dubbo TCP/Hessian2 or the supported Triple HTTP/JSON method. The .NET caller does not need to reference the Java interface JAR.
Let’s first look at how to use it, and then expand on what it can do.
The examples in this article are based on DubboNet 3.6.2. The current package target framework is
net6.0can be referenced by .NET 6 and above applications.

1. The simplest way to use
1. Install NuGet package
Execute in the existing .NET project directory:
dotnet add package DubboNet --version 3.6.2
2. A few lines of C# to call a Dubbo method
Assume that the Java side has published the interface com.example.api.HelloServicewhich has a method:
String sayHello(String name);
The service is registered in ZooKeeper, and the default root directory is /dubbo. The C# calling code is as follows:
using DubboNet.Clients;
using var client = new DubboClient("127.0.0.1:2181");
var result = await client.QueryAsync(
"com.example.api.HelloService#sayHello",
"DubboNet");
if (!result.QuerySuccess)
{
Console.WriteLine(result.ErrorMeaasge);
return;
}
Console.WriteLine(result.Result);
The entry here consists of “full Java interface name + # + method name”, and then directly pass the business parameters. DubboNet will try to select the corresponding signature from the method metadata, and there is no need to hand-write the parameter type every time it is called.
Automatic type inference with parameter calls also requires available method metadata (this dubbo service comes with it by default); if there is no metadata, the “explicit Java type” method introduced later can also be called.
The interfaces, methods, addresses and DTOs in this article are examples and need to be replaced with your own services.
3. No parameters and multiple parameters also use the same set of APIs
// 无参数方法。
var health = await client.QueryAsync(
"com.example.api.HealthService#check");
// 多参数方法,参数按 Java 方法的声明顺序传入。
var customer = await client.QueryAsync(
"com.example.api.CustomerService#getCustomer",
"customer-001",
1234L);
The public call entrance is unified as QueryAsync. When a single parameter itself is an array or collection, it will also be treated as one parameter and will not be accidentally split into multiple Java method parameters.
In actual applications, please reuse long-life-cycle DubboClientdo not create and release once for each request. above using Used to show the complete life cycle; in web applications or background services, it can be managed uniformly by the DI container or host, and then released when the application is closed.
2. DubboNet’s capabilities and advantages
| ability | Value to .NET callers |
|---|---|
| Does not rely on Java interface JAR | Use interface name, method name and parameters to complete the call, suitable for cross-language access and universal tools |
| ZooKeeper / Nacos service discovery | There is no need to maintain the IP of each Provider in the business code, and you can subscribe to node changes |
| Native Dubbo TCP + Hessian2 | pass $invoke Generalized calls to existing Dubbo services without relying on Telnet to execute business requests |
| Triple Java Interface Unary HTTP/JSON | Call the supported Dubbo 3 Triple interface through the unified client API |
| Metadata reading and type inference | Obtain method signatures, identify overloads, and reduce the work of manually filling in types |
| Explicit signatures vs. strongly typed responses | Taking into account the certainty of calls and the ease of use of C# business code |
| Six load balancing modes | Choose random, polling, least active, shortest response and other strategies based on traffic characteristics |
| Connection reuse and caching | Reuse registration center resources, Provider connections and method metadata to reduce repeated initialization |
| Optional consumer registration | Let the registration center see the interfaces referenced by the application and support running switches |
| MIT open source | You can view the implementation, submit issues, and use it in your own projects according to the license agreement. |
The native executor supports multiplexing concurrent requests using Request ID on a single TCP connection, without the need to create a new connection for each request.LeastActive、ShortestResponse Strategies such as this use the consumer’s local statistics and do not require the provider’s Telnet diagnostic port to be opened (the Telnet operation method is gradually deprecated in versions after 1.3.0).
Reusing resources as much as possible allows DubboNet to have very strong performance. Even if thousands of asynchronous requests are started at the same time during the performance test, only one client is needed.
3. Make calls more clear: Java types, strongly typed responses and complex parameters
1. When there are overloads or null parameters, explicitly specify the Java type
Automatic inference is suitable for simple, well-signed interfaces. However, the target Dubbo service may not have the metadata center enabled, or parameter inference may fail due to ambiguity and overloading.
In these cases, you can directly pass in the accurate Java signature:
var result = await client.QueryAsync(
"com.example.api.CustomerService#getCustomer",
new[] { "java.lang.String", "java.lang.Long" },
new object[] { "customer-001", 1234L });
There is a one-to-one correspondence between the type array and the parameter array. Common types can be written as int、long、java.lang.Integer、java.lang.String、java.util.List、java.util.Mapand the fully qualified name of the Java DTO.
Explicit signatures do not rely on metadata to succeed. This is a more certain way to access critical interfaces, complex overloads, or environments with incomplete metadata.
2. Directly read the C# response model
If you don’t want to manually parse JSON in your business code, you can specify the response type:
var result = await client.QueryAsync(
"com.example.api.CustomerService#getCustomer",
new[] { "java.lang.Long" },
new object[] { 1234L });
if (result.QuerySuccess && result.ResultModle is CustomerDto customer)
{
Console.WriteLine($"{customer.Id}: {customer.Name}");
}
else
{
Console.WriteLine(result.ErrorMeaasge);
}
The corresponding model can be defined as:
using System.Text.Json.Serialization;
public sealed class CustomerDto
{
[JsonPropertyName("id")]
public long Id { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
}
ResultModle pass System.Text.Json Deserialized, the original JSON remains in Result middle. When the attribute name or case is inconsistent, you can use it as above JsonPropertyName mapping.
ResultModleandErrorMeaasgeis the actual API spelling for the current version. Model deserialization errors are logged inErrorMeaasgemiddle;QuerySuccessIt does not itself represent the business response.codeorsuccessIt will definitely succeed.
3. POJOs, versions, groupings and attachments
You can use it when you need to transfer complex objects or specify service versions, groups and additional information. DubboInvocation:
using DubboNet.DubboService.DataModle;
var invocation = new DubboInvocation(
"com.example.api.CustomerService",
"save",
new[] { "com.example.api.dto.CustomerRequest" },
new object[]
{
new
{
name = "张三",
age = 28
}
})
{
Version = "1.0.0",
Group = "prod",
Attachments = new Dictionary
{
["traceId"] = Guid.NewGuid().ToString("N")
}
};
var result = await client.QueryAsync(invocation);
The native Dubbo generalized call will supplement the required parameters according to the POJO parameter type. class information. Object fields should correspond to Java DTOs, and complex nested structures should be verified on the real Provider first.
Version、Group It represents the service version and service grouping, not the Dubbo framework version, nor the Naming grouping of Nacos.traceId Whether this type of attachment participates in link tracking requires the server to cooperate in reading and processing.
4. Switch from ZooKeeper to Nacos
When using Nacos, use nacos:// Just specify the registration center at the beginning:
using var nacosClient = new DubboClient(
"nacos://127.0.0.1:8848",
new DubboClient.DubboClientConf
{
// Nacos Naming 分组,与 Provider 的注册配置保持一致。
DubboRootPath = "DEFAULT_GROUP",
// Nacos Config 中存放 Dubbo 元数据的分组,独立配置。
MetadataRootPath = "dubbo",
DubboRequestTimeout = 5_000
});
var result = await nacosClient.QueryAsync(
"com.example.api.HelloService#sayHello",
"hello-nacos");
Nacos can also specify namespace ID, authentication and other information through connection string query parameters. In actual projects, please read the credentials from the configuration or key management service and do not hardcode them into the source code.
Both registration centers use the same QueryAsync calling method;DubboClient The executor will be selected based on the Provider URL:
dubbo://: Classic Dubbo2 TCP frame + Hessian2 generalized call.tri://: Currently supported Triple Java Interface single request/response HTTP/JSON calls.
Need to pay attention to,Supporting Dubbo 3 Provider does not mean supporting all the capabilities of Dubbo 3. Currently, service discovery is still oriented to interface-level registration, and Triple also has clear boundaries, which will be explained separately at the end of the article.
5. Select load balancing according to business
Policies can be specified when creating a client:
using var balancedClient = new DubboClient(
"127.0.0.1:2181",
new DubboClient.DubboClientConf
{
NowLoadBalanceMode = DubboClient.LoadBalanceMode.LeastActive,
DubboRequestTimeout = 5_000
});
The following six modes are currently supported:
| model | main behavior |
|---|---|
Random |
Default strategy, randomly selected according to provider weight |
RoundRobin |
Smooth weighted polling |
LeastActive |
Prioritize the node with the smallest number of requests in transit observed by this client, and time-sharing weighting is random. |
P2CLoadBalance |
Extract two different nodes according to their weights, and then compare the number of local in-transit requests. |
ShortestResponse |
Based on the average successful response time, number of requests in transit and failure penalty location selection in the last 30 seconds |
ConsistentHash |
The same parameter key tries to hit the same node, which is suitable for requesting affinity scenarios. |
The number of activities and time consumption here areObservations local to the current consumernot the global load of the Provider. The shortest response mode is not simply selecting the node with the “fastest last request”.
Consistent Hash is especially easy to be misunderstood during stress testing: the current key is JSON after serialization of the complete parameter array. All requests are sent [0,0] When , falling to the same node is expected behavior; if you want to test the overall throughput of the cluster, you should choose a strategy that can disperse repeated requests, or prepare business keys with reasonable distribution.
The current consistent hash uses UTF-8, stable MurmurHash2 and 160 virtual nodes per node. It does not guarantee the same mapping as Java Dubbo’s Hash implementation, nor does it allocate virtual nodes in a positive weight ratio.
6. Not just calling: you can also read interface and type metadata
If you are developing Dubbo debugging tools, interface browsers or dynamic parameter editors, you often need to know “what methods the interface has and what types of parameters it has.”
DubboNet provides corresponding API:
// 查看一个方法的全部重载。
var overloads = await client.GetMethodMetadataAsync(
"com.example.api.CustomerService",
"getCustomer");
foreach (var method in overloads)
{
Console.WriteLine(
$"{method.ReturnType} {method.Name}" +
$"({string.Join(", ", method.ParameterTypes)})");
}
// 查看服务的全部方法签名。
var methods = await client.GetServiceMethodsMetadataAsync(
"com.example.api.CustomerService");
// 从元数据中心读取 POJO、集合、枚举等类型定义。
var types = await client.GetServiceTypesMetadataAsync(
"com.example.api.CustomerService");
Method signature first selects the metadata source according to the Provider version, and uses one-time Telnet if necessary. ls -l Rollback; after the metadata is successfully read, it will be cached and listen for relevant changes.
GetServiceTypesMetadataAsync then relies on the complete type definition in the metadata center and will not return to Telnet because ls -l There is no complete POJO structure. It provides the information reported by the Provider and will not complete the missing business definition out of thin air.
This enables DubboNet not only to be embedded in business applications, but also suitable as a calling basis for .NET Dubbo testing and management tools.
7. Enable consumer registration on demand and support hot switching
If you want the registration center and compatibility management platform to be able to see “which application refers to which interface”, you can enable consumer registration. It is turned off by default.
using DubboNet.Clients.DataModle;
DubboClient.ConfigureConsumerRegistration(new ConsumerRegistrationOptions
{
Enabled = true,
ApplicationName = "customer-admin-dotnet"
});
// 后续 QueryAsync 使用到的接口会在后台登记消费者记录。
// 运行期间关闭,不需要重建 DubboClient。
DubboClient.ConfigureConsumerRegistration(new ConsumerRegistrationOptions
{
Enabled = false
});
// 再次开启,继承之前的应用身份,并补登记仍存活客户端已引用的接口。
DubboClient.ConfigureConsumerRegistration(new ConsumerRegistrationOptions
{
Enabled = true
});
The above demonstration is three independent configuration operations. In actual use, they are executed at the corresponding business timing. There is no need to switch continuously immediately after startup.
This configuration isprocess levelit is not a switch exclusive to a certain client. After the configuration is updated, the background performs registration or deregistration according to the latest expected status. It will not wait for the network operation to be completed before returning, nor will it block RPC due to registration failure.
ZooKeeper uses temporary nodes and existing session mechanisms; Nacos uses temporary instances and the SDK’s connection maintenance mechanism. It also provides failure log, backoff retry and session recovery processing. The application should still release the client normally and cannot use heartbeat as a substitute for “automatically identifying whether the service is still in use”.
8. When debugging, you can also bypass the registration center and connect directly.
When the Provider’s IP, port and Java method signature are known, the native executor can be used directly:
using DubboNet.DubboService;
using DubboNet.DubboService.DataModle;
using var suite = new NativeDubboActuatorSuite(
"127.0.0.1",
20880,
new DubboActuatorSuiteConf
{
DubboRequestTimeout = 5_000
});
var invocation = new DubboInvocation(
"com.example.api.HelloService",
"sayHello",
new[] { "java.lang.String" },
new object[] { "hello-direct" });
var result = await suite.SendQuery(invocation);
Console.WriteLine(result.Result);
Direct connection is suitable for joint debugging and fixed-point troubleshooting, but it does not provide service discovery, load balancing or method signature inference. In an environment with Telnet open conditions, you can also use the relevant diagnostic API to read ls、status and other information.
9. Points that may need attention
- Verified combination: The native Dubbo path includes Dubbo 2.7.3, and the classic version of Dubbo 3.2.20/3.3.6
dubbo://Compatibility mode; Triple path verified Dubbo 3.3.6 Java Interface Unary HTTP/JSON. Other versions need to be tested in conjunction with the project compatibility instructions. - Running platform: The current package targets .NET 6 and no .NET Standard or .NET Framework targets are provided.
- service discovery: Supports ZooKeeper/Nacos interface-level registration, Dubbo 3 application-level service discovery has not yet been implemented.
- native serialization: Currently using Hessian2, it will not automatically switch to other serializers based on Provider configuration.
- Triple range: Protobuf/IDL, streaming calls, and full gRPC framing are not yet implemented, and the current implementation cannot be considered a universal gRPC client.
- Nacos multi-environment tool scenario: Due to the current underlying SDK life cycle limit, the same process can retain up to 32 different connection configurations. For details, see github README.
After accessing, you should also check “Call layer success” and “Business success” respectively: first look QuerySuccessand then look at the business status in the response. Do not directly implement infinite retries outside of non-idempotent write operations.
10. Which projects are suitable?
If you have the following needs, you may consider trying DubboNet:
- Use .NET management background to call existing Java Dubbo business services.
- Reuse existing Dubbo interfaces in C# background tasks and integrated programs.
- Develop Dubbo interface debugging, metadata browsing or stress testing tools.
- I hope to directly access the registration center instead of maintaining a list of Provider addresses on the caller side.
It encapsulates service discovery, signature resolution, connection reuse, load balancing and call result processing into a .NET client, so that cross-language calls do not have to start from the underlying protocol.
Start with a simple query interface to confirm the registration center, protocol and type signature, and then gradually connect to the real business. You are welcome to try it out and provide feedback on compatible scenarios, and you are also welcome to improve it together through Issues and PRs.
[]()