JNBridgePro — the fastest, easiest way to bridge Java and .NET in production. Generate proxies in minutes, call Java from C# (or C# from Java) with native syntax — trusted by enterprises worldwide.
Learn more ·
Download free trial If you need to call C# from Java, you’re facing one of the most common cross-platform integration challenges in enterprise software development. Whether you need to access a .NET library from a Java application, integrate with a legacy C# system, or bridge teams working across both platforms, this guide covers every viable approach to calling .NET from Java — with working code examples, performance analysis, and clear recommendations.
This is the companion guide to our post on how to call Java from C#. While the concepts are related, calling C# from Java presents unique challenges because Java applications must initiate communication with the .NET Common Language Runtime (CLR), which requires different tooling and techniques.
Can You Call C# Code from Java?
Yes, you can call C# code from Java using several proven methods. The most common approaches include exposing C# functionality through REST APIs, using gRPC for high-performance remote procedure calls, leveraging the Java Native Interface (JNI) with native C++ wrappers, running .NET executables as subprocesses, and using dedicated bridging tools like JNBridgePro that enable direct in-process communication between the Java Virtual Machine (JVM) and the .NET CLR.
The best approach depends on how tightly your Java and C# code needs to be coupled, your performance requirements, and whether you need the .NET code running in the same process as your Java application or as a separate service. Let’s explore each method in depth.
Why Call C# from Java?
Enterprise development teams face this integration challenge in several common scenarios:
- Accessing .NET-only libraries: Your Java application needs functionality that only exists in a C# or .NET library — a proprietary SDK, a Windows-specific API, or a specialized calculation engine built in .NET.
- Legacy system integration: Your organization has critical business logic in a C#/.NET codebase that must be consumed by newer Java applications without a full rewrite.
- Cross-team collaboration: One team develops in Java while another builds in C#, and their systems must share data and functionality in real time.
- Platform migration: You’re migrating from .NET to Java (or vice versa) and need both systems running simultaneously during the transition period.
- Best-of-breed architecture: You want to use the strongest libraries from each ecosystem — perhaps a Java-based data pipeline with a .NET-based machine learning model or financial calculation engine.
Understanding your specific use case helps determine which Java-.NET interoperability approach will serve you best.
Overview of All Approaches
When you need to invoke C# from Java, you’re fundamentally connecting two different runtime environments — the JVM (Java Virtual Machine) and the CLR (Common Language Runtime). Each approach handles this connection differently:
- Network-based approaches (REST, gRPC) keep Java and .NET in separate processes communicating over HTTP or TCP
- Native interop (JNI + native code) bridges through a compiled C/C++ layer that can load the CLR
- Subprocess execution runs the .NET application as a separate OS process
- In-process bridging (JNBridgePro) runs both the JVM and CLR in the same process with automatic proxy generation and type marshaling
Each method involves different trade-offs in latency, development complexity, runtime overhead, and long-term maintenance. The sections below provide code examples and analysis for each approach.
Approach 1: REST APIs and HTTP Services
The most widely discussed method for calling .NET from Java involves wrapping your C# code in HTTP endpoints using ASP.NET Core. Your Java application then sends HTTP requests to the .NET web service and receives JSON responses.
How It Works
- Create an ASP.NET Core Web API project exposing your C# functionality as REST endpoints
- Deploy the .NET service (IIS, Kestrel, Docker container, etc.)
- Use
HttpClient or a similar HTTP library in Java to call the endpoints
C# Service (ASP.NET Core):
[ApiController]
[Route("api/[controller]")]
public class CalculationController : ControllerBase
{
[HttpPost("risk-score")]
public ActionResult<RiskResult> CalculateRisk([FromBody] RiskInput input)
{
var engine = new RiskEngine();
var result = engine.Calculate(input.Portfolio, input.Parameters);
return Ok(result);
}
}
Java Client:
HttpClient client = HttpClient.newHttpClient();
String json = "{\"portfolio\": \"ACME-401K\", \"parameters\": {\"horizon\": 30}}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:5000/api/calculation/risk-score"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
RiskResult result = objectMapper.readValue(response.body(), RiskResult.class);
Pros
- Language-agnostic — standard HTTP communication that works between any two technology stacks
- Well-understood architecture with extensive tooling and documentation
- Independent scaling and deployment of Java and .NET components
- Easy to add authentication, rate limiting, and monitoring
Cons
- Network latency on every call — typically 5–50ms per request, even on localhost
- JSON serialization and deserialization overhead on both sides
- Requires deploying and maintaining a separate .NET web service
- Complex error handling across HTTP boundaries (timeouts, retries, circuit breakers)
- Not suitable for fine-grained, method-level integration requiring hundreds of calls per operation
REST works best when Java and .NET are genuinely separate services with infrequent, coarse-grained communication. If your Java code needs to call dozens of C# methods per user request, the accumulated network overhead becomes a serious bottleneck.
Approach 2: gRPC with Protocol Buffers
gRPC provides better performance than REST by using binary serialization (Protocol Buffers) and HTTP/2 for transport. It’s a strong choice when you need structured, strongly-typed communication between Java and .NET with better performance than REST.
How It Works
- Define your service contract in a
.proto file - Generate C# server code and Java client stubs from the contract
- Implement the service in C#, call it from Java
Proto Definition:
syntax = "proto3";
service RiskService {
rpc CalculateRisk (RiskInput) returns (RiskResult);
}
message RiskInput {
string portfolio_id = 1;
int32 horizon_days = 2;
}
message RiskResult {
double score = 1;
string rating = 2;
}
Java gRPC Client:
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 5000)
.usePlaintext()
.build();
RiskServiceGrpc.RiskServiceBlockingStub stub = RiskServiceGrpc.newBlockingStub(channel);
RiskResult result = stub.calculateRisk(
RiskInput.newBuilder()
.setPortfolioId("ACME-401K")
.setHorizonDays(30)
.build()
);
System.out.println("Risk score: " + result.getScore());
Pros
- 2–10x faster than REST (binary serialization, HTTP/2 multiplexing, persistent connections)
- Strongly typed contracts prevent interface drift between Java and .NET teams
- Built-in streaming support for large data transfers
- Excellent tooling in both Java and .NET
Cons
- Still network-bound — latency per call is lower than REST but still measured in milliseconds
- More complex setup than REST (protoc compilation, code generation pipeline)
- Contract management overhead —
.proto files must stay synchronized - Still requires running a separate .NET service process
- Not suitable for in-process, method-level calls to C# objects
Approach 3: JNI with a Native C++ Bridge
The Java Native Interface (JNI) is a framework built into the JVM that allows Java code to call native C/C++ functions. By writing a native C++ layer that hosts the .NET CLR (using CLR Hosting APIs or COM interop), you can create a bridge from Java through C++ into C#.
How It Works
- Write a Java class with
native method declarations - Implement the native methods in C++ using JNI
- In the C++ code, initialize the .NET CLR and invoke C# methods
- Return results back through C++ to Java
Java Side:
public class DotNetBridge {
static {
System.loadLibrary("DotNetBridge");
}
public native double calculateRiskScore(String portfolioId, int horizonDays);
}
C++ Bridge (simplified):
#include <jni.h>
#include <metahost.h>
#include <mscoree.h>
JNIEXPORT jdouble JNICALL Java_DotNetBridge_calculateRiskScore(JNIEnv *env, jobject obj, jstring portfolioId, jint horizon) {
// Initialize CLR, load assembly, invoke C# method
ICLRMetaHost *pMetaHost = NULL;
CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
// ... CLR hosting code, method invocation, result extraction ...
return riskScore;
}
Pros
- True in-process communication — no network latency
- Direct access to the CLR from within the JVM process
- Maximum control over the integration layer
Cons
- Extreme complexity — requires expertise in Java, C++, and C# plus JNI and CLR hosting APIs
- Manual memory management across three runtime environments (JVM, native heap, CLR)
- Platform-specific native code (Windows-only for CLR hosting; Mono/CoreCLR for cross-platform)
- Extremely difficult debugging — native crashes, memory leaks, and runtime conflicts
- Every new C# method requires corresponding C++ wrapper code
- Not viable for most enterprise teams without deep systems programming expertise
JNI is the technically low-level “correct” approach, but the development and maintenance cost is prohibitive for all but the most specialized use cases. Most teams that start down this path eventually abandon it for a higher-level solution.
Approach 4: Running .NET as a Subprocess
The simplest approach: run a .NET console application as a separate process from Java using ProcessBuilder, exchanging data through command-line arguments, standard I/O streams, or files.
ProcessBuilder pb = new ProcessBuilder("dotnet", "run", "--project", "RiskCalculator", "--", "ACME-401K", "30");
pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String result = reader.readLine();
int exitCode = process.waitFor();
System.out.println("Risk score: " + result);
Pros
- Dead simple to implement — no special libraries or frameworks needed
- Complete process isolation between Java and .NET
- Works with any .NET application that has a CLI interface
Cons
- Massive overhead per invocation — .NET runtime startup costs 100ms–2s per call
- Data exchange limited to strings (stdout/stdin) or files
- No shared state between invocations
- Impractical for frequent or real-time interactions
- Rudimentary error handling (exit codes and stderr parsing)
- Security concerns with passing sensitive data via command-line arguments
This approach only makes sense for infrequent, batch-style operations — for example, triggering a .NET report generator from a Java scheduling system once per hour.
Approach 5: In-Process Bridging with JNBridgePro
JNBridgePro takes a fundamentally different approach to Java-.NET interoperability: it runs both the JVM and the .NET CLR in the same process, generating Java proxy classes that let you call C# methods as if they were native Java code. This eliminates network latency, serialization overhead, and the need for complex C++ wrapper code.
How It Works
JNBridgePro’s proxy generation tool analyzes your .NET assemblies (DLL files) and creates corresponding Java classes containing proxy objects. These proxies handle all type marshaling between Java and .NET types automatically — including complex objects, collections, exceptions, and event callbacks.
At runtime, JNBridgePro manages an embedded .NET CLR within your Java process (or vice versa), routing method calls through an optimized shared-memory channel. Both garbage collectors are coordinated to prevent memory leaks and ensure proper object lifecycle management.
Key Capabilities
- Bidirectional calls: C# code can call back into Java, enabling event-driven and callback-based architectures
- Automatic type conversion: .NET
List<T> ↔ Java ArrayList, Dictionary<K,V> ↔ HashMap, and all primitive type mappings - Exception propagation: .NET exceptions become Java exceptions with complete stack traces preserved
- Coordinated garbage collection: No manual memory management — both runtimes’ garbage collectors work together
- Shared-memory transport: In-process communication with microsecond-level latency
- Support for modern .NET: Works with .NET Framework 4.x, .NET 6, 7, 8, and 9
- Support for modern Java: Compatible with Java 8 through Java 21+
For technical details, explore the JNBridgePro Developer Center.
Comparison Table: All C# from Java Integration Methods
| Approach | Latency per Call | Setup Complexity | Maintenance Burden | In-Process? | Best For |
|---|
| REST APIs | 5–50ms | Medium | Medium | No | Loosely coupled microservices |
| gRPC | 1–10ms | High | High | No | Structured cross-service communication |
| JNI + C++ + CLR Hosting | ~Microseconds | Very High | Very High | Yes | Low-level systems (expert teams only) |
| Subprocess (.NET CLI) | 100ms+ (.NET startup) | Low | Low | No | Infrequent batch tasks |
| JNBridgePro | ~Microseconds | Low | Low | Yes | Enterprise integration, frequent calls, production |
JNBridgePro delivers in-process performance comparable to JNI but without the complexity of maintaining C++ bridge code, manual cross-runtime memory management, or platform-specific native libraries.
Step-by-Step: Calling C# from Java with JNBridgePro
Here’s a complete walkthrough for using JNBridgePro to call C# methods from Java in a real project.
Step 1: Install JNBridgePro
Download JNBridgePro and install both the Java-side and .NET-side components. The installer includes the proxy generation tool, runtime libraries, and documentation.
Step 2: Generate Java Proxy Classes
Launch the JNBridgePro proxy generation tool and point it at your .NET assemblies (DLL files). The tool analyzes the .NET types and generates corresponding Java proxy classes for each C# class, interface, and enum you want to access.
Step 3: Add Proxies to Your Java Project
Add the generated proxy JAR file and the JNBridgePro runtime JAR to your Java project’s classpath (or Maven/Gradle dependencies).
Step 4: Configure the Runtime
Create a properties file to configure the JNBridgePro runtime:
# jnbcore.properties
dotnet.side=shared-memory
dotnet.assembly.path=./lib/MyDotNetLibrary.dll
dotnet.runtime=coreclr
Step 5: Call C# Methods from Java
With proxies generated, calling C# from Java looks like standard Java code:
import com.jnbridge.jnbcore.DotNetSide;
import MyDotNetLibrary.*; // Generated proxy package
public class Main {
public static void main(String[] args) {
// Initialize JNBridgePro runtime
DotNetSide.init("jnbcore.properties");
// Create .NET objects and call methods — just like native Java
RiskEngine engine = new RiskEngine();
RiskResult result = engine.Calculate("ACME-401K", 30);
System.out.println("Risk Score: " + result.getScore());
System.out.println("Rating: " + result.getRating());
System.out.println("Confidence: " + result.getConfidence());
// .NET collections work like Java collections
List<String> factors = result.getRiskFactors();
for (String factor : factors) {
System.out.println("Factor: " + factor);
}
}
}
No HTTP calls. No serialization code. No C++ wrappers. The .NET RiskEngine object behaves exactly like a native Java object, with full IDE support including code completion, type checking, and debugging.
When choosing how to call C# code from Java, performance characteristics vary dramatically across approaches:
Network-based approaches (REST, gRPC) add milliseconds of latency per call. For a business operation requiring 20 C# method calls, you’re accumulating 100ms–1,000ms of pure network overhead — before any actual computation. This compounds under load and introduces failure modes like connection exhaustion, timeouts, and service discovery issues.
Subprocess execution is the slowest option by far. Each invocation pays the full .NET runtime startup cost (100ms–2s), making it viable only for very infrequent operations.
JNI with CLR hosting offers excellent raw performance but carries enormous development risk. Incorrectly managed native references cause memory leaks and crashes that are extremely difficult to diagnose in production environments. Cross-runtime debugging requires specialized tools and deep expertise.
In-process bridging (JNBridgePro) delivers the optimal balance: microsecond-level method call latency with automatic memory management and full type safety. Because both the JVM and CLR share the same OS process, there’s no serialization overhead and no network stack involved — data passes through optimized shared-memory channels.
For applications requiring frequent Java-to-C# interaction (hundreds or thousands of method calls per operation), in-process approaches are the only viable option. Between the two in-process options (JNI and JNBridgePro), JNBridgePro eliminates months of C++ development and ongoing maintenance cost.
What About jni4net, IKVM, and Javonet?
Developers researching how to call C# from Java often encounter several alternative tools in Stack Overflow answers and older blog posts:
jni4net is an open-source library that bridges Java and .NET using JNI. While it appears in many search results and Stack Overflow discussions, the project has been largely unmaintained since 2015. It does not support modern .NET (Core, .NET 5+) and lacks documentation for current Java versions. It can work for simple prototypes on .NET Framework but is not suitable for production enterprise systems.
IKVM translates Java bytecode to .NET CIL, allowing Java code to run on the CLR. This is primarily useful in the opposite direction (running Java code in .NET), and has limitations with complex Java libraries that depend on JVM internals. The project has had periods of dormancy, though community forks exist.
Javonet is a commercial tool that provides cross-runtime method invocation between Java and .NET. It offers a different architecture and licensing model from JNBridgePro and is worth evaluating alongside it for your specific requirements.
GraalVM Native Image allows compiling C# (via .NET’s NativeAOT) or Java into native shared libraries. While technically interesting, it imposes severe restrictions on the source code (limited reflection, no dynamic class loading) and requires significant build pipeline complexity.
JNBridgePro remains the most mature and widely deployed dedicated solution for enterprise Java-.NET interoperability, with active development since 2001, professional technical support, and a comprehensive knowledge base.
When to Use Each Approach
Choose REST APIs when your Java and C# systems are genuinely separate services that communicate infrequently, and you’re already invested in a microservices architecture. This is the most common approach but rarely the best for tight integration.
Choose gRPC when you need better performance than REST with strong contract typing, but your Java and .NET components can operate as independent services with well-defined interfaces.
Choose JNI + C++ + CLR Hosting only if you have a team with deep expertise in C/C++, JNI, and CLR hosting APIs, and you need maximum control over a very narrow, well-defined integration surface. This is rarely the right choice.
Choose subprocess execution for simple, infrequent batch operations where a .NET CLI tool needs to be triggered from a Java scheduler or workflow engine. Avoid for anything requiring low latency or frequent calls.
Choose JNBridgePro when you need:
- Direct method-level access to C# classes from Java code
- In-process performance without network overhead or serialization
- Automatic type marshaling between .NET and Java types
- Bidirectional communication — Java calling .NET AND .NET calling back into Java
- Production-grade reliability with professional support and active maintenance
- Integration with existing .NET libraries or legacy C# systems without modification
For most enterprise scenarios involving Java-.NET interoperability — especially legacy system integration, gradual platform migration, and cross-platform library access — JNBridgePro provides the most productive and reliable path forward.
Ready to evaluate? Download a free trial of JNBridgePro to test with your own codebase, or contact the JNBridge team to discuss your integration requirements.
Frequently Asked Questions
Can Java call C# code directly?
Java cannot call C# code directly because Java runs on the JVM while C# runs on the .NET CLR — two separate runtime environments with different type systems, memory models, and execution engines. However, you can bridge them using network-based approaches (REST APIs, gRPC), native interop (JNI with CLR hosting), or dedicated bridging tools like JNBridgePro that run both runtimes in the same process and handle type conversion automatically.
What is the fastest way to call C# from Java?
The fastest way to call C# from Java is using in-process bridging, which runs the .NET CLR and the JVM within the same application process. This eliminates network latency and serialization overhead entirely, reducing per-call latency to microseconds rather than the milliseconds required by REST or gRPC. Both JNI (with CLR hosting) and JNBridgePro offer in-process performance, but JNBridgePro eliminates the need for C++ wrapper code and manual memory management.
Is jni4net still maintained?
No, jni4net has not been actively maintained since approximately 2015. While it still appears frequently in Stack Overflow answers and search results, it does not support modern .NET versions (.NET Core, .NET 5/6/7/8/9) and is not recommended for new projects. For actively maintained Java-.NET bridging solutions, consider JNBridgePro, which supports current versions of both .NET and Java with professional technical support.
Can you use .NET libraries in a Java application?
Yes, you can use .NET libraries (DLL assemblies) in a Java application. The most practical approaches are: (1) wrapping the .NET library in a REST API or gRPC service that your Java application calls over the network, or (2) using an in-process bridging tool like JNBridgePro that generates Java proxy classes from .NET assemblies, allowing you to call .NET library methods as if they were native Java methods.
How does JNBridgePro handle type conversion between Java and .NET?
JNBridgePro automatically marshals types between the JVM and CLR. Primitive types (int, double, string, boolean) are converted transparently. Complex types are represented as proxy objects — when you access a property or call a method on a proxy, JNBridgePro routes the call to the actual .NET object. Collections like List<T> and Dictionary<K,V> are converted to their Java equivalents (ArrayList, HashMap). Exceptions thrown in .NET are caught and re-thrown as Java exceptions with the original stack trace preserved.
What is the difference between calling C# from Java vs. calling Java from C#?
Both directions of Java-.NET interoperability use similar underlying techniques, but the tooling and setup differ. When calling Java from C#, the .NET application hosts the JVM. When calling C# from Java, the Java application hosts the CLR. JNBridgePro supports both directions with the same licensing and similar proxy generation workflows. For a detailed guide on the reverse direction, see our post on how to call Java from C#.
Does JNBridgePro work with .NET 8 and Java 21?
Yes, JNBridgePro supports modern versions of both platforms, including .NET 8, .NET 9, and Java 21+. It also maintains backward compatibility with .NET Framework 4.x and older Java versions (Java 8+). Check the JNBridgePro product page for the latest compatibility matrix.
Related Articles
Continue exploring Java-.NET integration: