Pass Data Between Java and .NET: Every Pattern
> TL;DR: You can pass data from Java to .NET using REST APIs (simplest), gRPC (fastest network option), message queues (async/decoupled), shared databases, file exchange, or in-process bridging with JNBridgePro (lowest latency, no network). Choose based on your latency, coupling, and complexity requirements. See the comparison table below.
You’ve got Java on one side and .NET on the other. Maybe it’s a legacy system you can’t rewrite. Maybe your team picked the best tool for each job and now those tools need to talk. Either way, you need to pass data from Java to .NET — or the other direction — and you need the right approach.
This guide covers every major pattern for Java/.NET data exchange, with code examples, a comparison table, and honest trade-off analysis. Whether you’re building a Java frontend with a .NET backend, trying to access a C# service from Java, or moving objects between runtimes, you’ll find your answer here.
Table of Contents
The Six Patterns for Java/.NET Data Exchange
Here’s the landscape. Each pattern occupies a different point on the latency-complexity spectrum:
Let’s dig into each one.
1. REST APIs: The Universal Glue
REST is the most common way to pass data from C# to Java (or vice versa). You expose an HTTP endpoint on one side and call it from the other. It’s language-agnostic by design — JSON serialization over HTTP doesn’t care what runtime produced it.
When to Use REST
- Greenfield integration with no unusual latency requirements
- Public or partner-facing APIs
- Teams that already have REST infrastructure (API gateways, load balancers)
Architecture: Java Frontend with .NET Backend
A typical setup uses a Java client for .NET services:
“`
┌─────────────────┐ HTTPS/JSON ┌──────────────────┐
│ Java Client │ ──────────────────────► │ ASP.NET Core │
│ (Spring Boot) │ ◄────────────────────── │ Web API │
└─────────────────┘ └──────────────────┘
│ │
Consumes JSON Serves JSON
via HttpClient via Controllers
The Java side sends HTTP requests; the .NET side returns JSON responses. This is the simplest way to run a C# backend from Java. For a deeper comparison of REST vs other approaches, see our guide on Bridge vs REST vs gRPC for Java/.NET integration.
Code Example: Java Consuming a .NET REST API
.NET Side — ASP.NET Core Controller:
`csharp
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpGet("{id}")]
public ActionResult
{
var order = _orderService.GetById(id);
if (order == null) return NotFound();
return Ok(order);
}
[HttpPost]
public ActionResult
{
var order = _orderService.Create(request);
return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}
}
`
Java Side — Calling the .NET API with HttpClient:
`java
import java.net.http.*;
import java.net.URI;
import com.google.gson.Gson;
public class DotNetOrderClient {
private final HttpClient client = HttpClient.newHttpClient();
private final Gson gson = new Gson();
private final String baseUrl = "https://dotnet-backend:5001/api/orders";
// Java consuming a .NET API — GET request
public Order getOrder(int id) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/" + id))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse
HttpResponse.BodyHandlers.ofString());
return gson.fromJson(response.body(), Order.class);
}
// Pass data from Java to .NET — POST request
public Order createOrder(OrderRequest orderReq) throws Exception {
String json = gson.toJson(orderReq);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse
HttpResponse.BodyHandlers.ofString());
return gson.fromJson(response.body(), Order.class);
}
}
`
Pros: Universal, well-understood, huge ecosystem of tooling.
Cons: Network overhead on every call. Serialization/deserialization cost. Not ideal for high-frequency, low-latency scenarios.
2. gRPC: Strongly-Typed, High-Performance RPC
gRPC uses HTTP/2 and Protocol Buffers (Protobuf) to deliver faster, more compact communication than REST. If you need to java consume a C# service with high throughput and strict contracts, gRPC is an excellent choice.
When to Use gRPC
- Internal microservices with high call volume
- Streaming data between Java and .NET
- Teams that want schema-first API design
For a detailed comparison, read gRPC vs JNBridgePro: When to Use Each.
Code Example: Java Client Calling a .NET gRPC Service
Shared .proto definition:
`protobuf
syntax = "proto3";
package orders;
service OrderService {
rpc GetOrder (OrderRequest) returns (OrderResponse);
rpc StreamOrders (OrderFilter) returns (stream OrderResponse);
}
message OrderRequest {
int32 id = 1;
}
message OrderResponse {
int32 id = 1;
string product = 2;
double amount = 3;
string status = 4;
}
message OrderFilter {
string status = 1;
}
`
.NET Side — gRPC service implementation:
`csharp`
public class OrderGrpcService : OrderService.OrderServiceBase
{
public override Task
OrderRequest request, ServerCallContext context)
{
var order = _repository.GetById(request.Id);
return Task.FromResult(new OrderResponse
{
Id = order.Id,
Product = order.Product,
Amount = order.Amount,
Status = order.Status
});
}
}
Java Side — gRPC client to access the C# service:
`java
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import orders.OrderServiceGrpc;
import orders.Orders.*;
public class GrpcOrderClient {
private final OrderServiceGrpc.OrderServiceBlockingStub stub;
public GrpcOrderClient(String host, int port) {
ManagedChannel channel = ManagedChannelBuilder
.forAddress(host, port)
.usePlaintext()
.build();
this.stub = OrderServiceGrpc.newBlockingStub(channel);
}
public OrderResponse getOrder(int id) {
OrderRequest request = OrderRequest.newBuilder()
.setId(id)
.build();
return stub.getOrder(request);
}
}
`
Pros: ~10x faster serialization than JSON, built-in streaming, strict contracts via .proto files.
Cons: Harder to debug (binary protocol), requires HTTP/2, more setup than REST.
3. Message Queues: Async and Decoupled
Message brokers like RabbitMQ or Apache Kafka let you pass data from .NET to Java without either side waiting for the other. The .NET service publishes a message; the Java service consumes it whenever ready.
When to Use Message Queues
- Event-driven architectures
- Fire-and-forget workflows (order placed, email triggered)
- Scenarios where Java and .NET systems operate at different speeds
- You need guaranteed delivery and retry logic
Architecture
``
┌──────────────┐ Publish ┌──────────────┐ Consume ┌──────────────┐
│ .NET Service │ ─────────────► │ RabbitMQ / │ ─────────────► │ Java Service │
│ (Producer) │ │ Kafka │ │ (Consumer) │
└──────────────┘ └──────────────┘ └──────────────┘
On the .NET side, use a library like MassTransit or the native RabbitMQ.Client. On the Java side, Spring AMQP or the Kafka consumer API handles consumption. The message body is typically JSON or Protobuf.
Pros: Fully decoupled, resilient to failures, scales independently.
Cons: Eventual consistency, added infrastructure (broker), harder to trace end-to-end.
4. Shared Database
Sometimes the simplest way to move data between Java and .NET is to share a database. One side writes; the other reads. No API to build, no broker to manage.
When to Use It
- Reporting systems reading data written by another platform
- Legacy systems where you can't modify the producing application
- Low-frequency batch reads
Risks
This pattern is seductive but dangerous at scale. Shared databases create tight coupling at the schema level. A column rename in the .NET app breaks the Java reader. There's no versioning, no contract, and no way to evolve independently.
Use it for read-only access from the consuming side, and consider views or dedicated schemas to insulate against changes.
Pros: No middleware, no network protocol to implement, immediate data availability.
Cons: Schema coupling, no access control beyond DB permissions, concurrent write conflicts.
5. File Exchange
File-based exchange — CSV, XML, JSON files dropped in a shared directory or cloud storage bucket — is the oldest integration pattern and still relevant for batch processing.
When to Use It
- Nightly batch imports/exports
- Regulatory data exchange with strict format requirements
- Integration with mainframe or legacy systems
Pros: Dead simple, auditable (files are artifacts), works with any technology.
Cons: Not real-time, error handling is manual, file format drift.
6. In-Process Bridging with JNBridgePro
Every pattern above requires a network boundary — HTTP calls, message brokers, or shared storage. JNBridgePro eliminates that boundary entirely by letting Java and .NET objects live in the same process and call each other directly.
How It Works
JNBridgePro creates proxy classes so that Java code can instantiate and call .NET objects (and vice versa) as if they were native. Under the hood, it manages cross-runtime communication via shared memory or TCP, but to the developer it looks like a normal method call. No marshaling code, no API layer, no serialization format to choose.
When to Use In-Process Bridging
- You need to call .NET libraries from Java without building an API layer
- Performance-critical paths where network latency is unacceptable
- Migrating from one platform to the other incrementally
- Using a .NET library that has no Java equivalent (or the reverse)
For the reverse direction, see our guide on how to call Java from C#.
Code Example: Java Calling .NET via JNBridgePro
`java
import com.jnbridge.jnbproxy.*;
// Initialize the bridge — one-time setup
com.jnbridge.jnbcore.DotNetSide.init(
new com.jnbridge.jnbcore.SharedMemChannelProperties());
// Use .NET's System.DateTime directly from Java
System.DateTime now = new System.DateTime();
now = System.DateTime.get_Now();
System.Console.WriteLine("Current time from .NET: " + now.ToString());
// Call a custom C# service class directly — no REST, no serialization
MyCompany.OrderService orderService = new MyCompany.OrderService();
MyCompany.Order order = orderService.GetOrder(42);
// Access properties like native Java
System.Console.WriteLine("Order total: " + order.get_TotalAmount());
`
The key insight: there's no JSON serialization, no HTTP overhead, no proto files. You're calling .NET methods from Java as if they were Java methods. JNBridgePro handles type mapping (C# decimal → Java BigDecimal, List → Java collections, etc.) automatically.
Pros: Lowest latency (no network), full access to .NET type system, no API layer to maintain, incremental migration path.
Cons: Requires JNBridgePro license, both runtimes must run on the same machine (or use TCP mode), adds a runtime dependency.
> 📦 Ready to try it? Download JNBridgePro free for 30 days and set up your first cross-runtime call in about 15 minutes.
Comparison Table
| Pattern | Latency | Setup Complexity | Data Format | Coupling | Best Use Case |
|---|---|---|---|---|---|
| REST API | Medium (1–50ms+) | Low | JSON | Loose | General-purpose, public APIs |
| gRPC | Low (0.5–10ms) | Medium | Protobuf | Medium | High-throughput internal microservices |
| Message Queue | High (50ms–seconds) | Medium–High | JSON/Protobuf | Very Loose | Async event-driven workflows |
| Shared Database | Low (query-dependent) | Low | Tabular | Tight | Read-only reporting, batch |
| File Exchange | Very High (minutes+) | Very Low | CSV/XML/JSON | None | Batch processing, regulatory |
| In-Process Bridge | Minimal (sub-ms) | Medium | Native types | Tight (runtime) | Direct library access, migration |
Serialization Formats: Choosing the Right Wire Format
How you serialize data matters as much as how you transport it. Here's when to use each format for Java/.NET interoperability:
JSON
The default for REST APIs. Human-readable, universally supported, and good enough for most workloads. Use it unless you have a specific reason not to.
- Libraries: Gson/Jackson (Java), System.Text.Json/Newtonsoft (C#)
- Weakness: Verbose, no schema enforcement, slow deserialization for large payloads
Protocol Buffers (Protobuf)
The standard for gRPC and a strong choice for message queues. Binary, compact, fast, with built-in schema evolution.
- Libraries: protobuf-java (Java), Google.Protobuf (C#)
- Weakness: Not human-readable, requires .proto
compilation step
XML
Still relevant in enterprise and legacy contexts (SOAP, healthcare HL7, financial FIX). Verbose but has strong schema validation via XSD.
- Libraries: JAXB (Java), System.Xml (C#)
- Weakness: Bulky, slow to parse compared to JSON or Protobuf
Binary (Custom)
When you control both sides and need maximum throughput — custom binary serialization. Examples include Apache Avro (popular with Kafka) and MessagePack.
- Use case: High-volume data pipelines, Kafka topics
- Weakness: Custom code, no interop without shared schema
Rule of thumb: Start with JSON. Move to Protobuf when performance matters. Use XML only when a standard requires it. Use binary for data pipelines.
Architecture Patterns: Java Frontend + .NET Backend
If you're building a Java frontend with a .NET backend, here are three proven architectures:
Pattern A: Direct REST/gRPC
``
┌─────────────┐ REST/gRPC ┌──────────────────┐
│ Java Web │ ────────────────────► │ .NET Core API │
│ App (JSF, │ ◄──────────────────── │ (Business Logic │
│ Spring MVC)│ │ + Data Access) │
└─────────────┘ └──────────────────┘
Simple, synchronous. The Java frontend acts as a Java client for .NET services. Works well when request-response is sufficient.
Pattern B: API Gateway + Backend Services
``
┌─────────────┐ ┌─────────────┐ ┌──────────────────┐
│ Java Web │ ──► │ API Gateway │ ──► │ .NET Service A │
│ Frontend │ │ (Kong/YARP) │ ──► │ .NET Service B │
└─────────────┘ └─────────────┘ │ Java Service C │
└──────────────────┘
Adds routing, auth, and rate limiting at the gateway. Better for microservice architectures where some services are .NET and some are Java.
Pattern C: In-Process (JNBridgePro)
``
┌──────────────────────────────────────────┐
│ Single Process (JVM) │
│ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ Java App │◄──►│ .NET Libraries │ │
│ │ (Frontend) │ │ (via Bridge) │ │
│ └─────────────┘ └─────────────────┘ │
└──────────────────────────────────────────┘
No network boundary. The Java application directly calls .NET business logic through JNBridgePro proxies. This is the highest-performance option and eliminates the need to build and maintain a separate API layer. Explore working examples in the JNBridgePro Developer Center.
Frequently Asked Questions
How Do I Pass Data From C# to Java Without Building an API?
You can pass data from C# to Java without an API using two main approaches: a shared database or file system (C# writes, Java reads), or an in-process bridge like JNBridgePro that lets Java call C# objects directly with no serialization or network protocol required.
JNBridgePro generates Java proxies for your .NET classes, so you access them like native Java objects. This eliminates the need to build, version, and maintain a REST or gRPC endpoint. It's especially useful when you need to call complex .NET libraries that would be painful to expose as an API. Learn more in our guide on calling C# from Java.
Can a Java Application Consume a .NET API Built with ASP.NET Core?
Yes — a .NET API built with ASP.NET Core exposes standard HTTP/JSON endpoints that any Java HTTP client can call. Use java.net.http.HttpClient, Apache HttpClient, OkHttp, or Spring's RestTemplate/WebClient` to send requests and deserialize responses with Jackson or Gson. It’s no different from calling any other REST API.
What’s the Best Way to Build a Java Frontend with a .NET Backend?
For most teams, REST or gRPC between the Java frontend and .NET backend is the straightforward choice. Use REST for simplicity and gRPC for performance. If you need the Java frontend to use .NET libraries directly (e.g., shared business logic), JNBridgePro lets you skip the API layer entirely. For event-driven architectures, put a message broker between them. See the architecture patterns section above.
Is It Possible to Run C# Backend Code From a Java Application?
Yes, in several ways. You can call a C# backend over REST or gRPC (the backend runs as a separate service). You can use JNBridgePro to load .NET assemblies directly into a Java process and call C# methods as if they were Java methods. You can also use message queues for asynchronous communication where Java sends a request and C# processes it.
Which Approach Has the Lowest Latency for Passing Data Between Java and .NET?
In-process bridging (JNBridgePro) has the lowest latency because it eliminates network hops entirely — method calls happen within the same process or over shared memory, achieving sub-millisecond response times. gRPC over localhost is the next fastest network-based option (0.5–10ms), followed by REST (1–50ms+). Message queues and file exchange trade latency for decoupling and resilience.
How Do You Secure Data Passed Between Java and .NET?
Security depends on the pattern. For REST and gRPC, use TLS encryption, OAuth 2.0 / JWT tokens for authentication, and API gateways for rate limiting. For message queues, enable broker-level TLS and authentication (e.g., RabbitMQ credentials, Kafka SASL). For in-process bridging, security is managed at the application level since data never crosses a network boundary. Read more about securing Java/.NET integrations.
Choosing the Right Pattern
There’s no single best answer. Here’s a decision framework:
- Need real-time, synchronous calls? → REST or gRPC
- Need async, decoupled processing? → Message queues
- Need direct access to .NET libraries from Java? → JNBridgePro
- Need batch data transfer? → File exchange or shared database
- Need the absolute lowest latency? → In-process bridging
- Need the simplest setup? → REST with JSON
Most real-world systems combine patterns. You might use REST for your public API, message queues for internal events, and JNBridgePro for a specific performance-critical integration where two runtimes share complex objects.
Get Started
If you’re evaluating how to connect Java and .NET in your organization, here’s what we recommend:
If direct, in-process communication fits your use case — especially if you need to call .NET libraries from Java without wrapping them in APIs — download JNBridgePro free for 30 days. It takes about 15 minutes to set up your first cross-runtime call.
Have questions about your specific integration scenario? Contact our engineering team — we’ve helped hundreds of organizations bridge Java and .NET.
JNBridge has been building Java/.NET interoperability tools since 2001. JNBridgePro is used by enterprises in finance, healthcare, government, and technology to integrate Java and .NET systems without rewrites.
