DEV Community

Cover image for How to Connect Java to C# and Run .NET Libraries from Java
JNBridge
JNBridge

Posted on • Originally published at jnbridge.com

How to Connect Java to C# and Run .NET Libraries from Java

If most "Java to C#" articles seem to assume the direction is always C# calling Java, this one flips the problem around.

Enterprise teams run into the reverse interop case all the time: a Java application needs a .NET library that already works, already carries business rules, and already has production history. Rewriting that library into Java is usually the most expensive way to solve the problem.

This is a developer-first walkthrough of the real options for calling .NET from Java, loading .NET DLLs in Java, and choosing the path that fits your call pattern instead of forcing everything through a service boundary.

The reverse interop problem

Java and .NET are both mature managed runtimes, but they do not share memory, type systems, or calling conventions.

That means the answer to "how do I use a C# library from Java?" is never just import.

Typical examples include:

  • A Java service that needs a .NET PDF renderer
  • A Java app that acquired a vendor DLL as part of a merger
  • A Java platform that needs a compliance or rules engine written in C#
  • A modernization project that is moving away from .NET, but not fast enough to rewrite everything at once

In each case, the business question is the same: how do we reuse the working code without turning the integration itself into a second project?

Four practical ways to connect Java to C

There are really four broad options.

1. Proxy-based bridging

This is the closest thing to "native" interoperability.

With a bridge such as JNBridgePro, you point the proxy generator at your .NET assemblies and generate Java proxy classes. Those proxies look like regular Java classes, but at runtime they forward calls into the CLR.

// Generated proxy for a .NET class
var renderer = new com.mycompany.dotnet.PdfRenderer();
byte[] pdf = renderer.render("template.html", data);
Files.write(Path.of("output.pdf"), pdf);
Enter fullscreen mode Exit fullscreen mode

The Java developer experiences a normal object model. The bridge handles CLR startup, type marshalling, and exception translation.

Use this when you have:

  • close-grained calls
  • object-heavy APIs
  • shared business logic that should not be wrapped in HTTP
  • a real need to reuse .NET code in-process

2. REST or gRPC

If the boundary is naturally service-oriented, this is often the cleanest answer.

Wrap the .NET component in an ASP.NET Core API or a gRPC service, then call it from Java.

This works well when:

  • the .NET component is already a service
  • the call pattern is coarse-grained
  • latency is acceptable
  • the Java and .NET pieces will remain independently deployed

The tradeoff is obvious: you gain architectural clarity, but you also pay for serialization, network hops, and another runtime to operate.

3. COM interop

COM still exists in a lot of enterprises, especially in Windows-only environments.

If you are already on Windows and have legacy components that expose COM interfaces, this may be a viable bridge. It is not the first choice for greenfield systems, but it still has a place in old enterprise estates.

The downside is just as obvious: it is Windows-only, and it is not the best long-term modernization path.

4. Native hosting via coreclr

In some cases, teams try to host the CLR natively from the Java side or build custom loaders around the runtime.

That can work for specialized scenarios, but it quickly becomes a maintenance problem if you need robust exception handling, version compatibility, thread management, and production support.

If the use case is enterprise production software, a supported bridge is usually the safer choice.

Why bridging usually beats rewriting

Rewriting sounds neat on slides because it promises purity.

In practice, rewrites usually force teams to rebuild:

  • business rules that already work
  • integration behavior discovered only in production
  • test coverage accumulated over years
  • edge cases nobody fully documented
  • deployment and rollback behavior that production already depends on

If the .NET code still has value, a bridge-first strategy gives you time to modernize deliberately instead of freezing delivery for a rewrite.

How to decide which path fits

Use this simple decision rule:

  • choose REST or gRPC if you already want a service boundary
  • choose COM only when you are trapped in a Windows-only legacy environment
  • choose native hosting only for specialized cases with very clear operational ownership
  • choose bridging when the integration is close-grained and you need Java and .NET to cooperate directly

If your Java code needs to call a .NET DLL many times per user action, forcing the call through HTTP is often the wrong abstraction.

If your integration is one request in, one response out, the service boundary is probably fine.

How to run a C# library from Java in production

The production checklist is more important than the demo.

When you load .NET DLLs in Java, make sure you have a plan for:

  • runtime version compatibility
  • exception translation
  • thread ownership and apartment models
  • startup latency
  • classpath and assembly loading
  • logging across both runtimes

The best bridge in the world does not help if the surrounding operational model is brittle.

A final rule of thumb

If the existing .NET library is useful, stable, and hard to replace, the first question should not be "how do we rewrite it in Java?"

The first question should be "how do we let Java use it safely?"

That is the real problem this article solves.

Top comments (0)