在不少企业级项目里,C# 和 Java 同时存在几乎是常态。比如核心系统是 Java 写的,但新模块用 .NET 重构;又或者公司并购后形成了双技术栈。问题也随之而来:CLR 和 JVM 是两套完全不同的运行时,内存模型、类型系统、垃圾回收机制都不一样,天生就不是为互操作设计的。
但现实不会等我们重写系统,所以'如何在 C# 中调用 Java'就成了一个绕不开的话题。下面这 5 种方案,都是在真实生产环境中验证过的做法。我会结合性能、复杂度和适用场景,做一次尽量客观的对比。
方案一:REST API(默认首选)
最常见、也最推荐的方式,就是把 Java 的能力封装成一个 REST 服务,然后在 C# 里通过 HTTP 调用。
using var client = new HttpClient();
var response = await client.GetAsync("http://localhost:8080/api/calculate");
var result = await response.Content.ReadFromJsonAsync<CalculationResult>();
这种方式非常适合粗粒度调用。例如用户点击一次按钮,触发一次跨系统计算,或者一个请求只需要调用 1~3 次远程接口。
优点很明显:架构清晰、天然解耦、便于部署和扩容,而且配合网关、监控、熔断、限流等机制都非常成熟。对于微服务体系来说,这几乎是标准答案。微软官方文档对 HttpClient 的使用方式也有详细说明①。
缺点也很现实:每一次调用都是一次网络往返。哪怕在内网环境,单次延迟通常也在 5~50 毫秒之间。如果一次业务操作里要调用 20 次以上接口,延迟会迅速叠加,用户就能明显感觉到卡顿。
简单说一句:调用次数少,用 REST 很舒服;调用次数多,就会开始痛。
方案二:gRPC(高性能替代方案)
如果你已经意识到 REST 的性能瓶颈,但又不想放弃服务化架构,可以考虑 gRPC。
var channel = GrpcChannel.ForAddress("http://localhost:5000");
var client = new CalculationService.CalculationServiceClient(channel);
var result = await client.CalculateAsync(new CalculationRequest { Value = 42 });
gRPC 基于 HTTP/2,使用 Protocol Buffers 做二进制序列化。相比 JSON,它的体积更小、解析更快。Google 官方也提供了性能基准测试说明②。
在实际项目中,单次调用延迟通常能降到 5~15 毫秒,比 REST 更稳定、更高效。同时它是强类型接口,通过 .proto 文件定义契约,跨团队协作时出错率更低。
但要注意两点:
第一,它本质上还是网络调用,延迟不可能为零。第二,需要维护 .proto 文件,并在两边同步生成代码,增加了一定开发成本。
所以,gRPC 更适合中等频率调用、对性能有要求但仍保持服务解耦的场景。
方案三:JNI(强烈不推荐)
理论上,你可以通过 JNI(Java Native Interface)让 Java 和本地代码交互③,然后再用 C++ 作为桥梁连接 CLR。
听起来很底层、很'硬核',但实际工程体验可以用四个字形容:。


