Calling .NET Code from Java: Every Approach Ranked (By Someone Who's Seen Them All)
Source: Dev.to
Typical Scenarios
- Legacy C# pricing engine – the company won’t rewrite
- .NET‑specific libraries – no Java equivalent
- Windows APIs – your Java app suddenly needs to call them
- .NET service – too tightly coupled to wrap in a clean API
The instinct is to ask “why not rewrite it in Java?”
The answer is usually money and time, so you integrate.
1. Wrap the .NET code in an ASP.NET Core Web API
C# side
[ApiController]
[Route("api/[controller]")]
public class PricingController : ControllerBase
{
[HttpPost("calculate")]
public ActionResult Calculate([FromBody] PriceRequest request)
{
var engine = new PricingEngine();
return Ok(engine.CalculatePrice(request));
}
}
Java side
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:5000/api/pricing/calculate"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
Works great when
- You’re making a handful of calls per user request.
- Clean separation; teams work independently.
- Easy to deploy and monitor.
Falls apart when
- One business operation requires many .NET method calls.
- Example: a single pricing calculation needs 10‑15 separate C# calls.
- At ~20 ms per HTTP round‑trip, that’s 200‑300 ms of pure network overhead before any computation happens.
2. Binary Serialization over HTTP/2 (gRPC)
Faster than REST, with strongly‑typed contracts via Protocol Buffers.
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", 5001)
.usePlaintext()
.build();
PricingServiceGrpc.PricingServiceBlockingStub stub =
PricingServiceGrpc.newBlockingStub(channel);
PriceResult result = stub.calculatePrice(
PriceRequest.newBuilder()
.setProductId("WIDGET-123")
.build());
Improvement over REST
- Roughly 5‑15 ms per call instead of 25‑75 ms.
- Typed contracts catch breaking changes at compile time rather than in production.
Still limited by
- Network latency. If your call pattern requires many round‑trips, you still accumulate milliseconds.
- You now have .proto files to maintain across both codebases.
3. Write C++ that Loads the .NET CLR and Exposes It to Java via JNI
I’ll be blunt: we’ve seen teams attempt this and abandon it after weeks of work.
The combination of JNI, C++ memory management, and .NET hosting APIs creates a debugging nightmare. One mis‑managed reference can crash the entire JVM with minimal diagnostics.
- When it makes sense – highly specialized embedded systems.
- For typical enterprise integration – the engineering cost rarely justifies it.
4. Run .NET as a Subprocess from Java
ProcessBuilder pb = new ProcessBuilder(
"dotnet", "run",
"--project", "PricingEngine",
"--", "WIDGET-123", "100"
);
Process process = pb.start();
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
String result = reader.readLine();
Good for
- Batch operations, scheduled jobs, anything where you call .NET infrequently.
- Scenarios that can tolerate 200 ms+ startup time.
Not good for
- Interactive workloads. The .NET runtime startup cost per invocation makes this impractical for user‑facing operations.
5. In‑Process Bridging (What JNBridge Pro Does)
Full disclosure — this is what JNBridge Pro does.
The JVM and CLR run in the same process, with generated proxy classes that make .NET objects accessible as native Java objects.
// This is actually calling C# under the hood
PricingEngine engine = new PricingEngine();
PriceResult result = engine.calculatePrice("WIDGET-123", 100);
Advantages
- Microsecond‑level call latency instead of millisecond‑level.
- For workloads requiring dozens of cross‑runtime calls per operation, the performance difference is dramatic.
Trade‑offs
- More complex deployment (two runtimes in one process, two garbage collectors).
- Commercial licensing costs.
- Best for teams that have measured their performance bottleneck and confirmed that network overhead is the problem.
When we tell customers NOT to use it
- If you’re making 2‑3 .NET calls per request, REST or gRPC is simpler and the latency difference won’t matter to users.
- Use the right tool rather than buying ours when you don’t need it.
6. Other Open‑Source / Commercial Options
| Tool | Status | Notes |
|---|---|---|
| jni4net | Effectively abandoned (≈2015) | Doesn’t support modern .NET versions. |
| IKVM | Community‑maintained fork exists | Compiles .NET assemblies to Java bytecode; struggles with heavy reflection or P/Invoke. |
| Javonet | Commercial | Different technical approach; worth evaluating – competition is good for the ecosystem. |
7. Numbers from Real Deployments
(Your mileage will vary based on payload size and network conditions.)
| Approach | Per‑call latency | 15 calls (typical complex operation) |
|---|---|---|
| REST | 20‑50 ms | 300‑750 ms |
| gRPC | 5‑15 ms | 75‑225 ms |
| Process exec | 200 ms+ | Not practical (spawn per call) |
| In‑process | <1 ms | ~10‑15 ms |
The question isn’t which is fastest — it’s which trade‑offs match your requirements.
8. Decision Guide – Three Questions
-
How many cross‑runtime calls per user request?
- 1‑5 calls → REST or gRPC
- 10+ calls → Evaluate in‑process bridging
-
What’s your latency tolerance?
- Seconds → REST (simplest)
- Hundreds of ms → gRPC
- Tens of ms → In‑process
-
How will this evolve?
- Planning to migrate away from .NET → REST (easiest to replace later)
- Both platforms are permanent → Invest in in‑process bridging (e.g., JNBridge Pro, Javonet)
Choose the approach that aligns with your performance needs, operational complexity, and long‑term strategy.
In Tighter Integration
Most teams land on REST or gRPC, and that’s the right call for most architectures.
I’d genuinely like to hear what integration patterns people are running. The Java/.NET interop space is more common than the industry acknowledges — it just doesn’t get talked about much because nobody’s excited about it. Drop your setup in the comments.
Disclosure: I work at JNBridge, which makes JNBridgePro — one of the integration tools discussed here. I’ve aimed to give an honest comparison of all approaches, including cases where our product isn’t the right choice.