<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: JNBridge</title>
    <description>The latest articles on DEV Community by JNBridge (@jnbridge).</description>
    <link>https://dev.to/jnbridge</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3784282%2Fae8a97d2-48bf-4d6d-908f-a3c634c52efe.png</url>
      <title>DEV Community: JNBridge</title>
      <link>https://dev.to/jnbridge</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jnbridge"/>
    <language>en</language>
    <item>
      <title>Java and .NET Without REST: Direct Interop Guide</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Thu, 24 Sep 2026 13:45:00 +0000</pubDate>
      <link>https://dev.to/jnbridge/java-and-net-without-rest-when-direct-interop-fits-better-4k1f</link>
      <guid>https://dev.to/jnbridge/java-and-net-without-rest-when-direct-interop-fits-better-4k1f</guid>
      <description>&lt;p&gt;REST is a good default for distributed systems, but it is not a law of nature. If a C# application needs to reuse an existing Java library—or Java needs to call a .NET assembly—turning every class operation into an HTTP endpoint can create more architecture than the problem requires.&lt;/p&gt;

&lt;p&gt;This guide compares the main ways to connect Java and .NET without treating REST as the automatic answer. The key is to decide whether you actually need a &lt;strong&gt;service boundary&lt;/strong&gt;, an &lt;strong&gt;asynchronous workflow&lt;/strong&gt;, or a &lt;strong&gt;runtime interoperability boundary&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with the boundary, not the transport
&lt;/h2&gt;

&lt;p&gt;When someone says “put a REST API around it,” they may be solving several different problems at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;isolating deployment and failures;&lt;/li&gt;
&lt;li&gt;making a capability available to many consumers;&lt;/li&gt;
&lt;li&gt;supporting clients in several languages;&lt;/li&gt;
&lt;li&gt;crossing a network boundary;&lt;/li&gt;
&lt;li&gt;reusing code that happens to run on another runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first four are legitimate reasons to design a service. The last one may only require direct interoperability.&lt;/p&gt;

&lt;p&gt;Suppose a .NET desktop application needs a mature Java calculation library. A REST wrapper means building and operating a Java service, inventing request and response contracts, mapping objects to JSON, handling versioning, securing an endpoint, and deploying another process. That can be worthwhile—but it should be an intentional architecture decision, not a reflex.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where REST wrappers become awkward
&lt;/h2&gt;

&lt;p&gt;REST works best with coarse-grained resources and operations. It becomes less natural when the real interface is object-oriented, internal, high-churn, or fine-grained.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapper explosion
&lt;/h3&gt;

&lt;p&gt;Every method that crosses the boundary needs an endpoint, payload schema, validation path, error contract, documentation, and tests. A team can accidentally build a second application whose main job is translating between two existing applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lost type semantics
&lt;/h3&gt;

&lt;p&gt;Rich Java and .NET types often become JSON documents plus handwritten mapping code. Overloads, enums, exceptions, object identity, callbacks, and references need new representations. The public contract may be clean, but it is not free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chattiness and latency
&lt;/h3&gt;

&lt;p&gt;An object model designed for local calls may make dozens of small calls during one business operation. Mapping each call to HTTP adds serialization, network handling, and request-level observability overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operational surface area
&lt;/h3&gt;

&lt;p&gt;A new service brings another deployment, health model, certificate path, log stream, monitoring target, and incident runbook. Those costs are justified for an intentional distributed boundary, but harder to defend for internal library reuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  The main alternatives
&lt;/h2&gt;

&lt;p&gt;Java and .NET can cooperate without REST in several ways. Each option solves a different class of problem.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;th&gt;Poor fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Runtime bridge with generated proxies&lt;/td&gt;
&lt;td&gt;Typed, direct reuse of Java or .NET classes&lt;/td&gt;
&lt;td&gt;Systems that must be fully independent services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Message queue&lt;/td&gt;
&lt;td&gt;Asynchronous workflows and event processing&lt;/td&gt;
&lt;td&gt;Immediate object-level responses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared database&lt;/td&gt;
&lt;td&gt;Simple state exchange and reporting&lt;/td&gt;
&lt;td&gt;Shared business logic or strong ownership boundaries&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Files or object storage&lt;/td&gt;
&lt;td&gt;Batch transfers and large artifacts&lt;/td&gt;
&lt;td&gt;Interactive operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom sockets or binary protocol&lt;/td&gt;
&lt;td&gt;Specialized latency or protocol requirements&lt;/td&gt;
&lt;td&gt;Teams that do not want to own protocol infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REST/gRPC&lt;/td&gt;
&lt;td&gt;Coarse-grained, independently deployed services&lt;/td&gt;
&lt;td&gt;Thin wrappers around many internal library calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rewrite&lt;/td&gt;
&lt;td&gt;Retiring an obsolete platform completely&lt;/td&gt;
&lt;td&gt;Stable systems whose behavior is expensive to reproduce&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The important distinction is that these patterns are not interchangeable. A queue is not a faster REST call. A shared database is not an API. A runtime bridge is not automatically a microservice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct interoperability with generated proxies
&lt;/h2&gt;

&lt;p&gt;A Java/.NET bridge can expose selected types from one runtime as generated proxy types in the other. The application uses local-looking classes and methods while the bridge handles communication, data conversion, object references, and exceptions.&lt;/p&gt;

&lt;p&gt;The build flow is usually:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select the Java classes or .NET assemblies that should cross the boundary.&lt;/li&gt;
&lt;li&gt;Generate proxies for that deliberately small surface.&lt;/li&gt;
&lt;li&gt;Add the generated JAR or assembly to the consuming project.&lt;/li&gt;
&lt;li&gt;Configure where the JVM and CLR sides run.&lt;/li&gt;
&lt;li&gt;Call through the proxies using normal Java or C# syntax.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, a .NET application consuming a Java risk engine can work with generated .NET proxy classes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generated proxies expose the selected Java API to C#.&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RiskEngine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RiskRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAccountId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPositions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;RiskResult&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calculate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Score: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getScore&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact bootstrap and proxy names depend on the selected bridge configuration, but the architectural point is stable: the consuming code sees a typed API instead of constructing an HTTP request for every method.&lt;/p&gt;

&lt;p&gt;The reverse direction works the same way. A Java application can consume selected .NET classes through generated Java proxies.&lt;/p&gt;

&lt;h2&gt;
  
  
  A bridge still needs a well-designed boundary
&lt;/h2&gt;

&lt;p&gt;Direct calls do not make boundary design irrelevant. Cross-runtime calls are more expensive than ordinary in-process calls, and a large proxy surface can couple two codebases too tightly.&lt;/p&gt;

&lt;p&gt;Use the same discipline you would apply to an internal API:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;expose stable, meaningful operations rather than every implementation class;&lt;/li&gt;
&lt;li&gt;send batches or domain objects instead of making one call per field;&lt;/li&gt;
&lt;li&gt;keep object ownership and lifetimes explicit;&lt;/li&gt;
&lt;li&gt;translate exceptions into failures the caller can act on;&lt;/li&gt;
&lt;li&gt;benchmark realistic object graphs, arrays, callbacks, and concurrency;&lt;/li&gt;
&lt;li&gt;regenerate proxies as part of a controlled upgrade workflow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful pattern is a small facade on the provider side. The facade can turn a chatty library into a few coarse operations without forcing the team to build and operate a web service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shared memory or separate processes?
&lt;/h2&gt;

&lt;p&gt;“Without REST” does not necessarily mean “everything in one process.” A bridge can support different runtime topologies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared-memory or in-process-style deployment&lt;/strong&gt; minimizes call overhead when both runtimes run on the same machine and share a lifecycle. It is useful for desktop software, local services, hardware-facing applications, and performance-sensitive library reuse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TCP/binary deployment&lt;/strong&gt; keeps the Java and .NET sides in separate processes or on separate machines. It adds isolation while retaining a proxy-based programming model. This is useful when operations require separate restart behavior, security controls, or deployment ownership.&lt;/p&gt;

&lt;p&gt;Topology should be an explicit choice. Do not use the lowest-latency option if process isolation is the more important requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  When REST or gRPC is still the right answer
&lt;/h2&gt;

&lt;p&gt;Avoiding REST should not become a goal by itself. A service boundary is the better design when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;several independent consumers need the capability;&lt;/li&gt;
&lt;li&gt;provider and consumer must deploy independently;&lt;/li&gt;
&lt;li&gt;the contract should be language-neutral and externally documented;&lt;/li&gt;
&lt;li&gt;the call is naturally coarse-grained;&lt;/li&gt;
&lt;li&gt;network isolation or organizational ownership is part of the architecture;&lt;/li&gt;
&lt;li&gt;horizontal scaling and service-level observability matter more than object-level reuse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;gRPC is often a stronger option than REST when the team wants an explicit schema, generated clients, streaming, and efficient binary transport. It still creates a service boundary, which is exactly what you want in those scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision framework
&lt;/h2&gt;

&lt;p&gt;Before adding another API, answer these questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Who is the consumer?&lt;/strong&gt; An internal Java/.NET application or a broad set of clients?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is being reused?&lt;/strong&gt; A stable library or an independently owned business capability?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How many operations are needed?&lt;/strong&gt; A few coarse calls or dozens of thin wrappers?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What is the call shape?&lt;/strong&gt; Batched domain operations or fine-grained object interaction?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who owns both sides?&lt;/strong&gt; One team or separate organizations with different release cycles?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What failure isolation is required?&lt;/strong&gt; Shared lifecycle or independently recoverable processes?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What must be observable?&lt;/strong&gt; Cross-runtime calls inside one application or a separately operated service?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Choose the smallest boundary that satisfies those requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production checklist
&lt;/h2&gt;

&lt;p&gt;Whichever pattern you choose, validate more than the happy path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;version the boundary and document compatibility;&lt;/li&gt;
&lt;li&gt;test arrays, collections, nulls, exceptions, and object lifetimes;&lt;/li&gt;
&lt;li&gt;measure realistic payloads and call frequency;&lt;/li&gt;
&lt;li&gt;define startup, shutdown, and restart behavior for both runtimes;&lt;/li&gt;
&lt;li&gt;centralize logs and correlation identifiers;&lt;/li&gt;
&lt;li&gt;test deployment upgrades and rollback;&lt;/li&gt;
&lt;li&gt;limit the exposed surface to what consumers actually need.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;Java and .NET do not need REST merely because they use different runtimes.&lt;/p&gt;

&lt;p&gt;Use REST or gRPC when you want a real distributed service. Use messaging for asynchronous workflows. Use files or shared storage for batch exchange. Use a generated-proxy bridge when the actual requirement is direct, typed reuse of existing Java or .NET code.&lt;/p&gt;

&lt;p&gt;The best architecture is not the one with the most fashionable transport. It is the one whose boundary matches the ownership, deployment, latency, and maintenance model of the system.&lt;/p&gt;

&lt;p&gt;The expanded source guide is available at &lt;a href="https://jnbridge.com/jnbridgepro/java-dotnet-without-rest-web-services" rel="noopener noreferrer"&gt;Java .NET Without REST&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>architecture</category>
      <category>interop</category>
    </item>
    <item>
      <title>Use NI-DAQmx from Java Without Owning a JNI Layer</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Mon, 21 Sep 2026 13:46:51 +0000</pubDate>
      <link>https://dev.to/jnbridge/use-ni-daqmx-from-java-without-owning-a-jni-layer-3a38</link>
      <guid>https://dev.to/jnbridge/use-ni-daqmx-from-java-without-owning-a-jni-layer-3a38</guid>
      <description>&lt;p&gt;If a Java application needs data from National Instruments hardware, the awkward part is rarely the acquisition logic. It is the missing Java API: NI-DAQmx supports C, .NET, Python, and LabVIEW, leaving JVM teams to decide whether to maintain native glue or put another boundary around the driver.&lt;/p&gt;

&lt;p&gt;This guide shows a third option: call the supported NI-DAQmx .NET class library from Java through generated proxies. The result is ordinary-looking Java code, block-based sample reads, and no project-specific JNI layer to rebuild whenever the driver or JDK changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the obvious JNI route becomes expensive
&lt;/h2&gt;

&lt;p&gt;JNI can call the NI-DAQmx C API, but it makes your team responsible for the entire native boundary.&lt;/p&gt;

&lt;p&gt;For every operation you expose, you need to own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Java native-method declaration;&lt;/li&gt;
&lt;li&gt;a C implementation that invokes the matching DAQmx function;&lt;/li&gt;
&lt;li&gt;conversions for task handles, channel strings, arrays, and timeouts;&lt;/li&gt;
&lt;li&gt;buffer allocation and channel/sample layout;&lt;/li&gt;
&lt;li&gt;translation of numeric status codes into useful Java exceptions;&lt;/li&gt;
&lt;li&gt;builds and tests across JDK, driver, architecture, and operating-system changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The failure model matters, too. A normal Java exception stays inside the runtime. A bad native pointer or buffer calculation can terminate the whole JVM.&lt;/p&gt;

&lt;p&gt;JNA and the Foreign Function &amp;amp; Memory API can remove some hand-written C, but they do not remove the ownership problem. You still bind and maintain a large procedural API, manage handles and buffers, and reproduce capabilities already present in NI's supported object-oriented .NET library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bridge to the maintained API instead
&lt;/h2&gt;

&lt;p&gt;The NI-DAQmx .NET assembly exposes classes such as &lt;code&gt;Task&lt;/code&gt;, &lt;code&gt;AnalogMultiChannelReader&lt;/code&gt;, channel collections, timing objects, and enums. A Java/.NET bridge can generate Java proxy classes from that assembly, preserving its object model on the JVM side.&lt;/p&gt;

&lt;p&gt;With JNBridgePro, the build-time flow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Point the proxy generator at &lt;code&gt;NationalInstruments.DAQmx.dll&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select the types your application needs.&lt;/li&gt;
&lt;li&gt;Generate a JAR containing Java proxies for those .NET types.&lt;/li&gt;
&lt;li&gt;Add the proxy JAR and bridge runtime to the Java project.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At runtime, Java calls a proxy. The bridge invokes the real .NET object and returns the result, including arrays and exceptions, across the runtime boundary.&lt;/p&gt;

&lt;p&gt;This is an important architectural distinction. The application is not calling a hand-built REST facade or duplicating the driver API. It is using the API that NI already maintains.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Java code looks like
&lt;/h2&gt;

&lt;p&gt;Here is a condensed acquisition example. It creates a four-channel voltage task, configures a finite sample clock, and reads 100 samples per channel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.jnbridge.jnbcore.DotNetSide&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;NationalInstruments.DAQmx.*&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AnalogInDemo&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;DotNetSide&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sharedmemory.properties"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;Task&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Get_AIChannels&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;CreateVoltageChannel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Dev1/ai0:3"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;AITerminalConfiguration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Differential&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="mf"&gt;10.0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;AIVoltageUnits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Volts&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Get_Timing&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;ConfigureSampleClock&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="mf"&gt;1000.0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;SampleClockActiveEdge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Rising&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;SampleQuantityMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FiniteSamples&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="mi"&gt;100&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;AnalogMultiChannelReader&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AnalogMultiChannelReader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Get_Stream&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="o"&gt;[][]&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ReadMultiSample&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Stop&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Dispose&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;printf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Read %d samples on %d channels%n"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;length&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Several details are worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proxy packages mirror .NET namespaces, so the code imports &lt;code&gt;NationalInstruments.DAQmx.*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;.NET properties appear through &lt;code&gt;Get_&lt;/code&gt; and &lt;code&gt;Set_&lt;/code&gt; methods, such as &lt;code&gt;Get_AIChannels()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The .NET &lt;code&gt;double[,]&lt;/code&gt; returned by &lt;code&gt;ReadMultiSample&lt;/code&gt; arrives as a Java &lt;code&gt;double[][]&lt;/code&gt;, organized by channel and sample.&lt;/li&gt;
&lt;li&gt;DAQmx exceptions can cross the bridge as catchable Java exceptions instead of becoming native-process failures.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keep the boundary coarse enough
&lt;/h2&gt;

&lt;p&gt;A bridge removes JNI maintenance, but it does not make boundary design irrelevant. The fastest call is still the call you do not make.&lt;/p&gt;

&lt;p&gt;DAQmx already encourages the right shape: configure work with a small number of calls, let the driver buffer samples, and read blocks. Do not make one cross-runtime call per sample. Read a block with &lt;code&gt;ReadMultiSample&lt;/code&gt;, then process that block in Java.&lt;/p&gt;

&lt;p&gt;The same rule applies to other hardware SDKs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a small facade when the vendor API is excessively chatty;&lt;/li&gt;
&lt;li&gt;return arrays or domain batches instead of individual values;&lt;/li&gt;
&lt;li&gt;keep object lifetimes explicit;&lt;/li&gt;
&lt;li&gt;dispose driver resources deterministically;&lt;/li&gt;
&lt;li&gt;test shutdown and failure paths, not only the successful read.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Choose the runtime topology deliberately
&lt;/h2&gt;

&lt;p&gt;There are two useful deployment shapes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared memory&lt;/strong&gt; hosts the CLR with the Java process and minimizes per-call overhead. It is a good fit for same-machine acquisition where latency matters and the two runtimes share a lifecycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TCP/binary&lt;/strong&gt; runs the .NET side in a separate process or on another machine. It adds a process boundary but provides stronger isolation and options such as TLS and class whitelisting.&lt;/p&gt;

&lt;p&gt;The Java API can remain the same across both topologies; configuration determines where the .NET side runs. That gives a team room to begin with a low-latency local deployment and later isolate the driver process if operations require it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test without physical hardware
&lt;/h2&gt;

&lt;p&gt;NI MAX can create simulated NI-DAQmx devices. A simulated device exposes the same channels and driver API as physical hardware and produces sample data, which makes it useful for development and CI-oriented smoke tests.&lt;/p&gt;

&lt;p&gt;A practical validation plan is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install NI-DAQmx with .NET support.&lt;/li&gt;
&lt;li&gt;Create a simulated device in NI MAX.&lt;/li&gt;
&lt;li&gt;Generate proxies from the installed DAQmx assembly.&lt;/li&gt;
&lt;li&gt;Run a finite analog-input read.&lt;/li&gt;
&lt;li&gt;Test an invalid channel to verify exception translation.&lt;/li&gt;
&lt;li&gt;Repeat a block read under realistic rate and channel-count settings.&lt;/li&gt;
&lt;li&gt;Replace the simulated device name with the physical device when hardware is available.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code path does not need to change when moving from a simulated device to the real one.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a service boundary is still better
&lt;/h2&gt;

&lt;p&gt;Direct bridging is not the answer to every integration problem.&lt;/p&gt;

&lt;p&gt;Put DAQmx behind REST, gRPC, or messaging when acquisition should be an independently deployed service, multiple remote consumers need a stable language-neutral contract, or asynchronous delivery is part of the design.&lt;/p&gt;

&lt;p&gt;Use a direct bridge when the Java application needs broad, low-latency access to the existing .NET object model and creating another service would be ceremony rather than intentional architecture.&lt;/p&gt;

&lt;p&gt;The decision is less about which transport is fashionable and more about the boundary you actually need: library reuse, process isolation, or a true distributed service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;The missing NI-DAQmx Java API does not force a choice between rewriting the acquisition layer and maintaining JNI glue forever.&lt;/p&gt;

&lt;p&gt;Generated proxies let Java use the supported .NET API, preserve type information, carry arrays and exceptions across the boundary, and keep application code focused on acquisition rather than native interop plumbing.&lt;/p&gt;

&lt;p&gt;The full walkthrough, including setup details and the working demo, is in the &lt;a href="https://jnbridge.com/jnbridgepro/ni-daqmx-java-without-jni" rel="noopener noreferrer"&gt;original NI-DAQmx Java guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>hardware</category>
      <category>interop</category>
    </item>
    <item>
      <title>5 Production-Safe Ways to Connect C# and Java</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Thu, 17 Sep 2026 13:44:56 +0000</pubDate>
      <link>https://dev.to/jnbridge/5-production-safe-ways-to-connect-c-and-java-and-how-to-choose-3mk1</link>
      <guid>https://dev.to/jnbridge/5-production-safe-ways-to-connect-c-and-java-and-how-to-choose-3mk1</guid>
      <description>&lt;p&gt;Most C#-to-Java demos make the first call look easy. Production is where the real questions appear: who owns the contract, what crosses the boundary, what happens when one runtime fails, and how much operational machinery does the integration add?&lt;/p&gt;

&lt;p&gt;This guide compares five approaches that actually survive production—generated proxies, REST, messaging, gRPC, and custom sockets—and gives you a practical way to choose among them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;Start with the boundary your system needs, not a favorite technology.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If your requirement is...&lt;/th&gt;
&lt;th&gt;Start with...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Call an existing Java library directly from C#&lt;/td&gt;
&lt;td&gt;Generated proxy bridge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Keep Java and .NET as independently deployed services&lt;/td&gt;
&lt;td&gt;REST or gRPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process work asynchronously and absorb traffic spikes&lt;/td&gt;
&lt;td&gt;Message queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Share a typed streaming contract between services&lt;/td&gt;
&lt;td&gt;gRPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Implement a specialized binary protocol with unusual latency constraints&lt;/td&gt;
&lt;td&gt;Custom sockets&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The distinction between a &lt;strong&gt;library boundary&lt;/strong&gt; and a &lt;strong&gt;service boundary&lt;/strong&gt; is the most important decision in this entire comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Generated proxies and runtime bridging
&lt;/h2&gt;

&lt;p&gt;A Java/.NET bridge generates C# proxy classes from Java bytecode. Your .NET code calls those proxies as typed objects while the bridge handles JVM startup, method invocation, object identity, marshalling, exceptions, and garbage-collection coordination.&lt;/p&gt;

&lt;p&gt;The application code can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;com.example.analytics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AnalyticsEngine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;computeScore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"customer-42"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Score: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AnalyticsEngine&lt;/code&gt; is a generated .NET proxy backed by the real Java object. Developers get normal C# method signatures and IDE completion instead of string-based reflection calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use this when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Java asset is a JAR or library, not a service;&lt;/li&gt;
&lt;li&gt;C# must call many Java classes or methods;&lt;/li&gt;
&lt;li&gt;calls are frequent or latency-sensitive;&lt;/li&gt;
&lt;li&gt;complex object graphs, callbacks, or exceptions cross the boundary;&lt;/li&gt;
&lt;li&gt;you want to reuse Java code without designing an API wrapper around every operation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Watch for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JDK and .NET version compatibility;&lt;/li&gt;
&lt;li&gt;JVM lifecycle and classpath configuration;&lt;/li&gt;
&lt;li&gt;platform-specific deployment details;&lt;/li&gt;
&lt;li&gt;choosing between an in-process or remote bridge topology.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JNBridgePro is one production-oriented implementation of this model. It supports generated proxies and both in-process shared-memory and TCP/binary deployment options.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. REST API
&lt;/h2&gt;

&lt;p&gt;REST is the familiar service-boundary choice. Put the Java logic behind Spring Boot, Quarkus, Jakarta EE, or another HTTP server, then call it with &lt;code&gt;HttpClient&lt;/code&gt; from .NET.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net.Http.Json&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HttpClient&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;BaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://analytics.internal/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"scores"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;CustomerId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"customer-42"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnsureSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ScoreResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;REST works best when the Java component should already be an independently deployed service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use this when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the call is coarse-grained;&lt;/li&gt;
&lt;li&gt;HTTP latency is acceptable;&lt;/li&gt;
&lt;li&gt;teams need independent deployment and scaling;&lt;/li&gt;
&lt;li&gt;the contract should be language-neutral and externally visible;&lt;/li&gt;
&lt;li&gt;your organization already operates HTTP services well.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Watch for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON serialization differences such as &lt;code&gt;BigDecimal&lt;/code&gt; versus &lt;code&gt;decimal&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;retries that accidentally duplicate non-idempotent work;&lt;/li&gt;
&lt;li&gt;API versioning and backward compatibility;&lt;/li&gt;
&lt;li&gt;authentication, tracing, rate limits, and another network failure mode;&lt;/li&gt;
&lt;li&gt;endpoint sprawl when a C# application needs broad access to a Java library.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;REST is a strong architecture when you really want a service. It is expensive ceremony when you only need to reuse a local Java API.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Message queues and event streams
&lt;/h2&gt;

&lt;p&gt;With RabbitMQ, Kafka, Azure Service Bus, or a similar broker, C# publishes a command or event and Java consumes it asynchronously.&lt;/p&gt;

&lt;p&gt;This changes more than the transport. The caller no longer gets an immediate method return; the architecture becomes eventually consistent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use this when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;work can happen asynchronously;&lt;/li&gt;
&lt;li&gt;producers and consumers need independent scaling;&lt;/li&gt;
&lt;li&gt;bursts should be buffered instead of overwhelming Java workers;&lt;/li&gt;
&lt;li&gt;replay, fan-out, or durable event history matters;&lt;/li&gt;
&lt;li&gt;temporary consumer downtime must not lose work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Watch for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;idempotency and duplicate delivery;&lt;/li&gt;
&lt;li&gt;dead-letter queues and poison messages;&lt;/li&gt;
&lt;li&gt;schema evolution;&lt;/li&gt;
&lt;li&gt;ordering guarantees;&lt;/li&gt;
&lt;li&gt;observability across an asynchronous workflow;&lt;/li&gt;
&lt;li&gt;correlation when a later event represents the result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not add a queue to imitate a synchronous method call. Use one because asynchronous processing is part of the business and operational model.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. gRPC
&lt;/h2&gt;

&lt;p&gt;gRPC provides a typed remote procedure call boundary. You define messages and services in Protocol Buffers, then generate both C# and Java clients or servers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;Analytics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;ComputeScore&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ScoreRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ScoreReply&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;ScoreRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;ScoreReply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="na"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compared with REST/JSON, gRPC typically offers a more compact wire format, generated contracts, HTTP/2 multiplexing, and first-class streaming.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use this when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java and .NET are separate internal services;&lt;/li&gt;
&lt;li&gt;both teams can share and govern a &lt;code&gt;.proto&lt;/code&gt; repository;&lt;/li&gt;
&lt;li&gt;streaming or high call volume matters;&lt;/li&gt;
&lt;li&gt;generated clients are preferable to hand-maintained HTTP models.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Watch for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;protobuf compatibility rules and field-number discipline;&lt;/li&gt;
&lt;li&gt;deadlines, cancellation, and retry policies;&lt;/li&gt;
&lt;li&gt;HTTP/2 support through every proxy and load balancer;&lt;/li&gt;
&lt;li&gt;debugging and browser visibility compared with JSON;&lt;/li&gt;
&lt;li&gt;the fact that this is still a network boundary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;gRPC solves service-to-service communication well. It does not remove the operational cost of running another service.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Custom sockets or named pipes
&lt;/h2&gt;

&lt;p&gt;At the lowest level, a C# process and Java process can exchange bytes over TCP, Unix domain sockets, or named pipes. You control framing, encoding, batching, backpressure, and connection pooling.&lt;/p&gt;

&lt;p&gt;That control is the reason to choose custom sockets—and the reason to avoid them by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use this when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the protocol already exists;&lt;/li&gt;
&lt;li&gt;a device or vendor format requires it;&lt;/li&gt;
&lt;li&gt;payload framing or latency requirements cannot be met by supported alternatives;&lt;/li&gt;
&lt;li&gt;the team has long-term ownership of both ends.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Watch for everything:&lt;/strong&gt; reconnect behavior, partial reads, framing, timeouts, authentication, encryption, version negotiation, backpressure, observability, and graceful shutdown.&lt;/p&gt;

&lt;p&gt;A fast prototype can become a private integration framework your team must maintain indefinitely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Boundary&lt;/th&gt;
&lt;th&gt;Type safety&lt;/th&gt;
&lt;th&gt;Typical call style&lt;/th&gt;
&lt;th&gt;Operational cost&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Proxy bridge&lt;/td&gt;
&lt;td&gt;Library/runtime&lt;/td&gt;
&lt;td&gt;Generated APIs&lt;/td&gt;
&lt;td&gt;Fine-grained, synchronous&lt;/td&gt;
&lt;td&gt;Low to medium&lt;/td&gt;
&lt;td&gt;Direct Java library reuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REST&lt;/td&gt;
&lt;td&gt;Network service&lt;/td&gt;
&lt;td&gt;OpenAPI or models&lt;/td&gt;
&lt;td&gt;Coarse-grained request/response&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Public or conventional HTTP services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Message queue&lt;/td&gt;
&lt;td&gt;Async broker&lt;/td&gt;
&lt;td&gt;Schema-dependent&lt;/td&gt;
&lt;td&gt;Commands and events&lt;/td&gt;
&lt;td&gt;Medium to high&lt;/td&gt;
&lt;td&gt;Event-driven workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gRPC&lt;/td&gt;
&lt;td&gt;Network service&lt;/td&gt;
&lt;td&gt;Protobuf-generated&lt;/td&gt;
&lt;td&gt;RPC and streaming&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Internal typed services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom socket&lt;/td&gt;
&lt;td&gt;Process/network&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Protocol-specific&lt;/td&gt;
&lt;td&gt;Very high&lt;/td&gt;
&lt;td&gt;Specialized protocols&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Latency numbers vary too much by payload, topology, TLS, serialization, and infrastructure to choose from a generic benchmark. Measure your real object sizes and call patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision tree you can use in an architecture review
&lt;/h2&gt;

&lt;p&gt;Ask these questions in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Is the Java component already an intentional service?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If yes, keep that boundary and choose REST or gRPC.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Does the caller need an immediate result?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If no, consider a queue or event stream.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Is the asset fundamentally a library that C# needs to use directly?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If yes, evaluate generated proxies before building service wrappers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Do you need streaming or a shared cross-language RPC contract?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If yes, gRPC is usually a better fit than REST.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Does an external protocol force raw sockets?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If no, avoid owning custom transport infrastructure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How many Java APIs must C# reach?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Broad, object-heavy access strongly favors a bridge. A few coarse operations fit a service boundary better.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Production checks that demos usually omit
&lt;/h2&gt;

&lt;p&gt;Whichever approach you choose, test failure behavior before performance tuning.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What happens when Java starts slowly or is unavailable?&lt;/li&gt;
&lt;li&gt;Can a call be retried safely?&lt;/li&gt;
&lt;li&gt;How are Java exceptions represented in C#?&lt;/li&gt;
&lt;li&gt;Who owns schema or proxy regeneration after an upgrade?&lt;/li&gt;
&lt;li&gt;Can logs and traces follow one request across the boundary?&lt;/li&gt;
&lt;li&gt;How are credentials and transport encryption managed?&lt;/li&gt;
&lt;li&gt;Which runtime shuts down first during deployment?&lt;/li&gt;
&lt;li&gt;Can you reproduce the production topology in CI?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For bridge-based calls, test arrays, collections, callbacks, exceptions, and object identity—not just strings and integers. For service boundaries, test timeouts, partial failures, backward compatibility, and overload behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final rule of thumb
&lt;/h2&gt;

&lt;p&gt;If C# needs to reuse Java as a library, treat it as an interoperability problem and evaluate generated proxies first.&lt;/p&gt;

&lt;p&gt;If Java and .NET should be independently deployed services, use REST or gRPC intentionally. If the workflow is truly asynchronous, use a broker. Reach for custom sockets only when the protocol itself is part of the requirement.&lt;/p&gt;

&lt;p&gt;The safest production design is usually the one whose boundary matches the system you actually have—not the trendiest transport.&lt;/p&gt;

&lt;p&gt;For the longer architecture guide and implementation details, see the &lt;a href="https://jnbridge.com/jnbridgepro/csharp-to-java-integration" rel="noopener noreferrer"&gt;original JNBridge article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Related reading on Dev.to
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/jnbridge/bridge-vs-rest-vs-grpc-how-to-choose-your-javanet-communication-method-54lk"&gt;Bridge vs REST vs gRPC: How to Choose Your Java/.NET Communication Method&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/jnbridge/run-java-from-c-5-methods-with-code-examples-2g6h"&gt;Run Java from C#: 5 Methods with Code Examples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/jnbridge/java-net-in-docker-kubernetes-3-architecture-patterns-that-actually-work-3em0"&gt;Java + .NET in Docker and Kubernetes: 3 Architecture Patterns&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Calling a Java Logging Package from .NET</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Mon, 14 Sep 2026 13:50:33 +0000</pubDate>
      <link>https://dev.to/jnbridge/calling-a-java-logging-package-from-net-24nh</link>
      <guid>https://dev.to/jnbridge/calling-a-java-logging-package-from-net-24nh</guid>
      <description>&lt;p&gt;Most Java/.NET interop demos stop at returning a string. Logging is a better test: it touches a real Java library, configuration, object marshalling, and two runtimes that need to produce one coherent output.&lt;/p&gt;

&lt;p&gt;This walkthrough uses JNBridgePro 12.1 to call Apache log4j from C#. The .NET and Java code both write through the same Java logging stack, so their messages appear together under one configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are building
&lt;/h2&gt;

&lt;p&gt;The finished console application does three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;calls log4j classes directly from C# through generated .NET proxies&lt;/li&gt;
&lt;li&gt;invokes an existing Java class whose &lt;code&gt;doIt()&lt;/code&gt; method also logs through log4j&lt;/li&gt;
&lt;li&gt;runs with either an in-process shared-memory bridge or a separate Java process over TCP/binary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The example uses log4j, but the workflow is the same for any Java JAR or class library that a .NET application needs to reuse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;You will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Java JDK&lt;/li&gt;
&lt;li&gt;the .NET SDK and, for this demo target, the .NET Framework 4.8 targeting pack&lt;/li&gt;
&lt;li&gt;JNBridgePro 12.1 (an evaluation license is enough)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;log4j.jar&lt;/code&gt;, &lt;code&gt;log4j-core.jar&lt;/code&gt;, and the compiled &lt;code&gt;loggerDemo.JavaClass&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sample files ship in &lt;code&gt;demos\tutorials\logDemo.zip&lt;/code&gt;. This walkthrough assumes they are unpacked under &lt;code&gt;C:\NewGui\LogDemo&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Build the Java classpath in JNBProxy
&lt;/h2&gt;

&lt;p&gt;Launch JNBProxy and choose &lt;strong&gt;Create new .NET → Java project&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Add both log4j JARs to the classpath, then add the folder containing the &lt;code&gt;loggerDemo&lt;/code&gt; package. You can drag the files and folder into the classpath panel or use &lt;strong&gt;Edit classpath…&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Next, load the types:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;strong&gt;Add Classes from JAR&lt;/strong&gt; for &lt;code&gt;log4j.jar&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Repeat for &lt;code&gt;log4j-core.jar&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;Add Classes from Classpath&lt;/strong&gt; for &lt;code&gt;loggerDemo.JavaClass&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Leave &lt;strong&gt;Include supporting classes&lt;/strong&gt; enabled.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Select the classes you want to expose, click &lt;strong&gt;Add+&lt;/strong&gt;, and build the proxy assembly as &lt;code&gt;LoggerDemoProxy.dll&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The generated assembly gives C# a typed surface for the Java APIs. Package names become .NET namespaces, and Java methods appear as methods on the proxy classes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Export a runnable .NET project
&lt;/h2&gt;

&lt;p&gt;From the exposed-proxies gear menu, choose &lt;strong&gt;Export demo project…&lt;/strong&gt; and select the proxy DLL you just built.&lt;/p&gt;

&lt;p&gt;Name the project &lt;code&gt;LoggerDemo&lt;/code&gt;, choose &lt;strong&gt;.NET Framework 4.8 (Windows)&lt;/strong&gt;, and export it. JNBProxy can also export projects for modern .NET on Windows or Linux.&lt;/p&gt;

&lt;p&gt;One easy mistake matters here: do not give the project the same name as the proxy assembly. A project called &lt;code&gt;LoggerDemoProxy&lt;/code&gt; would emit an assembly that collides with &lt;code&gt;LoggerDemoProxy.dll&lt;/code&gt;. Keeping the project as &lt;code&gt;LoggerDemo&lt;/code&gt; avoids that runtime failure.&lt;/p&gt;

&lt;p&gt;The export includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LoggerDemo.sln&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;a C# console project with references to the proxy assembly and JNBridge runtime&lt;/li&gt;
&lt;li&gt;shared-memory and TCP configuration files&lt;/li&gt;
&lt;li&gt;the Java-side runtime and classpath files&lt;/li&gt;
&lt;li&gt;&lt;code&gt;env.bat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;one-click build/run scripts for both communication modes&lt;/li&gt;
&lt;li&gt;a README explaining the generated layout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That starter project is useful beyond demos: it provides a known-good baseline before you fold the bridge into a larger solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Prove the generated bridge first
&lt;/h2&gt;

&lt;p&gt;Set &lt;code&gt;JAVA_HOME&lt;/code&gt; in &lt;code&gt;env.bat&lt;/code&gt;. The script derives the 64-bit &lt;code&gt;jvm.dll&lt;/code&gt; path used by shared-memory mode; you can also confirm that path in the generated &lt;code&gt;App.config&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;buildAndRunSharedMem.bat&lt;/code&gt; before adding application logic. The exported stub round-trips a call through &lt;code&gt;java.lang.Object&lt;/code&gt;. If it prints a Java hash string, the JVM, classpath, and proxy runtime are connected.&lt;/p&gt;

&lt;p&gt;If you instead get &lt;code&gt;ClassNotFoundException&lt;/code&gt; or &lt;code&gt;NoClassDefFoundError&lt;/code&gt;, check for a moved JAR, an incorrect class-folder root, or missing read permissions. Fix the classpath before debugging the C# code.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Call log4j from C
&lt;/h2&gt;

&lt;p&gt;Replace the generated &lt;code&gt;Program.cs&lt;/code&gt; with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;org.apache.log4j&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;java.lang&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;loggerDemo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;LoggerDemo&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.jnbridge.demos.logger.LoggerDemo"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;STAThread&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;BasicConfigurator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JavaString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Entering application"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

            &lt;span class="n"&gt;DotNetClass&lt;/span&gt; &lt;span class="n"&gt;dotNetClass&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;DotNetClass&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;JavaClass&lt;/span&gt; &lt;span class="n"&gt;javaClass&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JavaClass&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;dotNetClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;javaClass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doIt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JavaString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Exiting application"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DotNetClass&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt; &lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.jnbridge.demos.logger.DotNetClass"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JavaString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Logged from .NET"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are three details worth noticing.&lt;/p&gt;

&lt;p&gt;First, the &lt;code&gt;using&lt;/code&gt; directives mirror Java package names: &lt;code&gt;org.apache.log4j&lt;/code&gt;, &lt;code&gt;java.lang&lt;/code&gt;, and &lt;code&gt;loggerDemo&lt;/code&gt;. The application calls &lt;code&gt;Category&lt;/code&gt;, &lt;code&gt;BasicConfigurator&lt;/code&gt;, and &lt;code&gt;JavaClass&lt;/code&gt; as typed APIs, with IntelliSense available in Visual Studio.&lt;/p&gt;

&lt;p&gt;Second, the strings passed to &lt;code&gt;info()&lt;/code&gt; and &lt;code&gt;debug()&lt;/code&gt; are wrapped in &lt;code&gt;java.lang.JavaString&lt;/code&gt;. Those overloads expect a Java &lt;code&gt;Object&lt;/code&gt;; a CLR string is not itself a Java object, while the proxy wrapper is.&lt;/p&gt;

&lt;p&gt;Third, the loop alternates a .NET-originated logging call with &lt;code&gt;JavaClass.doIt()&lt;/code&gt;. Both ultimately use the same log4j configuration and output.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;buildAndRunSharedMem.bat&lt;/code&gt; again. You should see the Java and .NET messages interleaved in one console log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shared memory versus TCP
&lt;/h2&gt;

&lt;p&gt;The exported project supports two topologies.&lt;/p&gt;

&lt;p&gt;Shared-memory mode loads the JVM into the .NET process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dotNetToJavaConfig&lt;/span&gt;
    &lt;span class="na"&gt;scheme=&lt;/span&gt;&lt;span class="s"&gt;"sharedmem"&lt;/span&gt;
    &lt;span class="na"&gt;jvm64=&lt;/span&gt;&lt;span class="s"&gt;"C:\Program Files\Java\jdk-11\bin\server\jvm.dll"&lt;/span&gt;
    &lt;span class="na"&gt;jvm32=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
    &lt;span class="na"&gt;jnbcore=&lt;/span&gt;&lt;span class="s"&gt;"jnbcore.jar"&lt;/span&gt;
    &lt;span class="na"&gt;bcel=&lt;/span&gt;&lt;span class="s"&gt;"bcel-6.10.0.jar"&lt;/span&gt;
    &lt;span class="na"&gt;classpath=&lt;/span&gt;&lt;span class="s"&gt;".;log4j.jar;log4j-core.jar;C:\NewGui\LogDemo"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the simplest and fastest option when both runtimes can live in one process. The JVM starts automatically before the first proxy call.&lt;/p&gt;

&lt;p&gt;TCP/binary mode keeps Java in a separate process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dotNetToJavaConfig&lt;/span&gt;
    &lt;span class="na"&gt;scheme=&lt;/span&gt;&lt;span class="s"&gt;"jtcp"&lt;/span&gt;
    &lt;span class="na"&gt;host=&lt;/span&gt;&lt;span class="s"&gt;"localhost"&lt;/span&gt;
    &lt;span class="na"&gt;port=&lt;/span&gt;&lt;span class="s"&gt;"8085"&lt;/span&gt;
    &lt;span class="na"&gt;useSSL=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;
&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this topology, the Java classpath moves to the Java-side configuration. &lt;code&gt;buildAndRunTCP.bat&lt;/code&gt; builds the .NET project, starts the Java side, runs the demo, and shuts Java down afterward. TCP mode also supports SSL and class whitelisting for tighter deployment boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reusable pattern
&lt;/h2&gt;

&lt;p&gt;The useful part of this demo is not log4j itself. It is the repeatable integration sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;add the Java JARs and class folders&lt;/li&gt;
&lt;li&gt;expose the required classes and generate typed proxies&lt;/li&gt;
&lt;li&gt;export a working starter project&lt;/li&gt;
&lt;li&gt;prove the bridge before adding application code&lt;/li&gt;
&lt;li&gt;choose shared memory or TCP based on deployment needs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That lets a .NET team reuse a proven Java library without translating it, wrapping every method in HTTP, or hiding integration details behind string-based runtime calls.&lt;/p&gt;

&lt;p&gt;The complete illustrated walkthrough, including every JNBProxy screen and the generated project layout, is available in the &lt;a href="https://jnbridge.com/jnbridgepro/log4j-from-dotnet-demo-guide" rel="noopener noreferrer"&gt;original JNBridge guide&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>logging</category>
    </item>
    <item>
      <title>C# and Java Interprocess Communication: Sockets, APIs, and Bridges Compared</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Thu, 13 Aug 2026 13:23:32 +0000</pubDate>
      <link>https://dev.to/jnbridge/c-and-java-interprocess-communication-sockets-apis-and-bridges-compared-bf4</link>
      <guid>https://dev.to/jnbridge/c-and-java-interprocess-communication-sockets-apis-and-bridges-compared-bf4</guid>
      <description>&lt;p&gt;When Java and C# need to cooperate, a lot of teams jump straight to "just expose a REST API." Sometimes that is the right answer. Sometimes it is a workaround for the wrong boundary.&lt;/p&gt;

&lt;p&gt;If your C# and Java systems need to exchange a small amount of business data, REST or gRPC may be the cleanest path. If they need to move work asynchronously, messaging fits better. If they are already separate services, sockets can still make sense in specialized environments. And if the real problem is that one runtime needs to call the other's library or object model directly, a bridge is often the better fit.&lt;/p&gt;

&lt;p&gt;This is the practical comparison I wish more teams made before they committed to the wrong architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first question: what kind of boundary do you actually have?
&lt;/h2&gt;

&lt;p&gt;Most interprocess communication choices are not about language at all. They are about the shape of the integration boundary.&lt;/p&gt;

&lt;p&gt;Ask a few simple questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the Java side a service already?&lt;/li&gt;
&lt;li&gt;Does the C# side need synchronous answers?&lt;/li&gt;
&lt;li&gt;Is the interaction coarse-grained or fine-grained?&lt;/li&gt;
&lt;li&gt;Do the runtimes need to share object models?&lt;/li&gt;
&lt;li&gt;Is the work better handled as a command, an event, or a direct method call?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you answer those, the right integration pattern usually becomes obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sockets: useful when you need full protocol control
&lt;/h2&gt;

&lt;p&gt;Raw sockets are the low-level option. Java has &lt;code&gt;java.net.Socket&lt;/code&gt;, and .NET has &lt;code&gt;System.Net.Sockets&lt;/code&gt;. Both can move bytes efficiently over TCP, and both give you complete control over framing, serialization, and connection behavior.&lt;/p&gt;

&lt;p&gt;That makes sockets a reasonable choice when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you already have a custom protocol&lt;/li&gt;
&lt;li&gt;latency matters and every layer counts&lt;/li&gt;
&lt;li&gt;the payloads are small and predictable&lt;/li&gt;
&lt;li&gt;the environment is tightly controlled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The downside is that sockets are not an application protocol. Your team owns the hard parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;framing and message boundaries&lt;/li&gt;
&lt;li&gt;serialization and versioning&lt;/li&gt;
&lt;li&gt;timeouts and reconnect logic&lt;/li&gt;
&lt;li&gt;authentication and encryption&lt;/li&gt;
&lt;li&gt;diagnostics and observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the protocol is already a strategic asset, that control is valuable. If not, sockets can turn into a maintenance tax very quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  REST APIs: best for coarse-grained service boundaries
&lt;/h2&gt;

&lt;p&gt;REST is still the default answer for a reason. It is easy to understand, easy to debug, firewall-friendly, and widely supported by both Java and .NET tooling.&lt;/p&gt;

&lt;p&gt;REST works well when the C# and Java systems are intentionally separate services and the interaction is business-level rather than object-level. Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create an order&lt;/li&gt;
&lt;li&gt;look up inventory&lt;/li&gt;
&lt;li&gt;submit a document for processing&lt;/li&gt;
&lt;li&gt;request a calculation result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;REST is especially attractive when operations teams need visibility. Logs are easy to inspect, requests can be replayed with curl or Postman, and API gateways know what to do with HTTP.&lt;/p&gt;

&lt;p&gt;The tradeoff is that REST is also a wrapper. It adds serialization, network hops, and a service boundary even when what you really want is direct reuse of an existing library.&lt;/p&gt;

&lt;h2&gt;
  
  
  gRPC: strong contracts with better performance than REST
&lt;/h2&gt;

&lt;p&gt;If REST feels too loose or too chatty, gRPC is often the next place to look. It gives you typed contracts, generated clients, and efficient binary transport over HTTP/2.&lt;/p&gt;

&lt;p&gt;gRPC is a strong fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Java and .NET apps are independent services&lt;/li&gt;
&lt;li&gt;both teams can agree on a schema-first contract&lt;/li&gt;
&lt;li&gt;you want streaming or lower-latency calls&lt;/li&gt;
&lt;li&gt;you want more structure than ad hoc JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The catch is operational compatibility. HTTP/2, TLS, ingress proxies, load balancers, and service meshes all need to behave nicely. If your environment is already service-oriented, that is manageable. If not, it can become a distraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Messaging: best when the two runtimes should not wait on each other
&lt;/h2&gt;

&lt;p&gt;Messaging solves a different problem. Instead of asking one side to wait for the other, you publish work and let a consumer process it later.&lt;/p&gt;

&lt;p&gt;This is a great fit for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;workflows&lt;/li&gt;
&lt;li&gt;event-driven systems&lt;/li&gt;
&lt;li&gt;telemetry pipelines&lt;/li&gt;
&lt;li&gt;background jobs&lt;/li&gt;
&lt;li&gt;batch processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Common brokers include RabbitMQ, Kafka, Azure Service Bus, and ActiveMQ. A C# service can enqueue work for Java consumers, or Java can emit events that C# systems react to later.&lt;/p&gt;

&lt;p&gt;Messaging improves resilience, but it changes the programming model. You now need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;idempotent handlers&lt;/li&gt;
&lt;li&gt;retry policies&lt;/li&gt;
&lt;li&gt;dead-letter queues&lt;/li&gt;
&lt;li&gt;correlation IDs&lt;/li&gt;
&lt;li&gt;schema evolution&lt;/li&gt;
&lt;li&gt;monitoring and alerting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use messaging when the business process is naturally asynchronous. Do not force it in when the user expects an immediate response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bridges: best when you need direct Java and .NET API reuse
&lt;/h2&gt;

&lt;p&gt;Sometimes the real need is not service-to-service communication. Sometimes one runtime needs the other runtime's library, SDK, or object model.&lt;/p&gt;

&lt;p&gt;That is where a bridge fits.&lt;/p&gt;

&lt;p&gt;With a proxy-based bridge such as JNBridgePro, a Java app can call .NET classes or a C# app can call Java classes without wrapping everything in HTTP. The generated proxies let developers work with familiar types and method calls while the bridge handles runtime startup, marshalling, and exception translation.&lt;/p&gt;

&lt;p&gt;This is often the right answer when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the target is a library, not a service&lt;/li&gt;
&lt;li&gt;the calls are fine-grained&lt;/li&gt;
&lt;li&gt;the integration needs to feel native&lt;/li&gt;
&lt;li&gt;you want to reuse working code instead of rewriting it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a very different problem from "two services need to exchange JSON."&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;th&gt;Strengths&lt;/th&gt;
&lt;th&gt;Tradeoffs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sockets&lt;/td&gt;
&lt;td&gt;Custom protocols, controlled environments&lt;/td&gt;
&lt;td&gt;Low overhead, flexible, universal&lt;/td&gt;
&lt;td&gt;You own everything above TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REST&lt;/td&gt;
&lt;td&gt;Independent service boundaries&lt;/td&gt;
&lt;td&gt;Simple, observable, familiar&lt;/td&gt;
&lt;td&gt;More overhead than binary protocols&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gRPC&lt;/td&gt;
&lt;td&gt;Typed service contracts&lt;/td&gt;
&lt;td&gt;Fast, structured, good for streaming&lt;/td&gt;
&lt;td&gt;Needs HTTP/2-compatible infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Messaging&lt;/td&gt;
&lt;td&gt;Async workflows and events&lt;/td&gt;
&lt;td&gt;Decoupled, resilient, scalable&lt;/td&gt;
&lt;td&gt;Not a direct function call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bridge&lt;/td&gt;
&lt;td&gt;Direct library/object reuse&lt;/td&gt;
&lt;td&gt;Native-style calls, less wrapper code&lt;/td&gt;
&lt;td&gt;Tighter runtime coupling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to choose without overthinking it
&lt;/h2&gt;

&lt;p&gt;A simple rule of thumb works surprisingly well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;choose &lt;strong&gt;REST&lt;/strong&gt; if the boundary is already a business service&lt;/li&gt;
&lt;li&gt;choose &lt;strong&gt;gRPC&lt;/strong&gt; if you want a stronger contract and better performance&lt;/li&gt;
&lt;li&gt;choose &lt;strong&gt;messaging&lt;/strong&gt; if the work should be asynchronous&lt;/li&gt;
&lt;li&gt;choose &lt;strong&gt;sockets&lt;/strong&gt; only when you need protocol-level control&lt;/li&gt;
&lt;li&gt;choose a &lt;strong&gt;bridge&lt;/strong&gt; when one runtime needs to call the other's APIs directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your Java code needs to invoke a .NET DLL repeatedly during one user action, forcing that through HTTP is usually the wrong abstraction. If the systems are already separate services, then HTTP or gRPC is often the cleanest route.&lt;/p&gt;

&lt;h2&gt;
  
  
  A production checklist that matters more than the diagram
&lt;/h2&gt;

&lt;p&gt;Before you commit to any pattern, check the boring stuff:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who owns retries and timeouts?&lt;/li&gt;
&lt;li&gt;What happens on partial failure?&lt;/li&gt;
&lt;li&gt;How do you log exceptions across runtimes?&lt;/li&gt;
&lt;li&gt;What is the deployment story?&lt;/li&gt;
&lt;li&gt;Who supports the system at 2 a.m.?&lt;/li&gt;
&lt;li&gt;How expensive is versioning?&lt;/li&gt;
&lt;li&gt;Do you need sync or async semantics?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The wrong choice is usually the one that looks elegant on a slide but becomes hard to operate in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;The best C# and Java interprocess communication pattern is not the one that sounds most modern. It is the one that matches the boundary you actually have.&lt;/p&gt;

&lt;p&gt;If the problem is direct library reuse, a bridge is often the cleanest answer. If the problem is service integration, REST or gRPC may be better. If the problem is async work, use messaging. If you need complete protocol control, sockets still have a place.&lt;/p&gt;

&lt;p&gt;The important thing is to choose intentionally, not by habit.&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>interop</category>
    </item>
    <item>
      <title>C# Lucene Options: Run Java Lucene from .NET or Use Lucene.NET?</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Mon, 10 Aug 2026 13:22:37 +0000</pubDate>
      <link>https://dev.to/jnbridge/c-lucene-options-run-java-lucene-from-net-or-use-lucenenet-3knj</link>
      <guid>https://dev.to/jnbridge/c-lucene-options-run-java-lucene-from-net-or-use-lucenenet-3knj</guid>
      <description>&lt;p&gt;If you work in .NET long enough, you eventually run into a Java library that is too useful to ignore. Apache Lucene is a classic example: it is the engine behind a lot of serious search systems, and it still shows up in enterprise environments where the Java side is already proven and the .NET side needs to catch up.&lt;/p&gt;

&lt;p&gt;That leaves teams with a practical question: should you switch to Lucene.NET, wrap Lucene behind REST, or call the real Java library from C#?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally published on the &lt;a href="https://jnbridge.com/jnbridgepro/use-java-libraries-in-dotnet-core" rel="noopener noreferrer"&gt;JNBridge blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The three realistic Lucene options
&lt;/h2&gt;

&lt;p&gt;For most .NET teams, the decision comes down to one of three paths:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Tradeoffs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lucene.NET&lt;/td&gt;
&lt;td&gt;You can live with the .NET port and its API/feature differences&lt;/td&gt;
&lt;td&gt;Port parity can lag Java Lucene versions and behavior may not match exactly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REST wrapper around Java Lucene&lt;/td&gt;
&lt;td&gt;You already run Lucene as a separate service&lt;/td&gt;
&lt;td&gt;Adds serialization, latency, and another deployment boundary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JNBridgePro calling Java Lucene directly&lt;/td&gt;
&lt;td&gt;You need the actual Java Lucene library from .NET&lt;/td&gt;
&lt;td&gt;Requires bridge setup, but keeps direct access to the real Java API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In practice, the right answer depends on what you need to preserve.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you only need “search-like” behavior, Lucene.NET may be enough.&lt;/li&gt;
&lt;li&gt;If you need the exact Java Lucene implementation, a bridge is usually the safer production choice.&lt;/li&gt;
&lt;li&gt;If your Java Lucene code is already a service, REST may be acceptable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Lucene.NET is sometimes enough
&lt;/h2&gt;

&lt;p&gt;Lucene.NET is the obvious place to start when your team wants a .NET-native codebase and can tolerate differences from Java Lucene.&lt;/p&gt;

&lt;p&gt;It can be a good fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the feature set you need exists in the .NET port&lt;/li&gt;
&lt;li&gt;you do not need exact Java parity&lt;/li&gt;
&lt;li&gt;the search component is not tightly coupled to other Java libraries&lt;/li&gt;
&lt;li&gt;the team prefers a pure .NET stack for operational simplicity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But Lucene.NET is still a port. That matters when you depend on a very specific analyzer, query behavior, or version-specific Lucene capability that exists first in Java.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a REST wrapper is often a compromise
&lt;/h2&gt;

&lt;p&gt;A REST API can be the easiest way to put a boundary around Lucene if the Java side is already running separately.&lt;/p&gt;

&lt;p&gt;That works well when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lucene is already part of a separate Java service&lt;/li&gt;
&lt;li&gt;calls are coarse-grained&lt;/li&gt;
&lt;li&gt;latency is acceptable&lt;/li&gt;
&lt;li&gt;you want clear service boundaries and independent deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The downside is that search workloads often want more than a few coarse service calls. If your .NET application needs frequent access to Lucene internals, REST can turn a library integration problem into a distributed systems problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why direct Java access is often the best production answer
&lt;/h2&gt;

&lt;p&gt;If your team needs the real Java Lucene library inside a .NET application, JNBridgePro is the option worth evaluating first.&lt;/p&gt;

&lt;p&gt;JNBridgePro runs the JVM alongside the CLR and generates .NET proxies for Java classes. That means your C# code can talk to Java Lucene as if it were a native .NET dependency, without rewriting the search logic or hiding the API behind a custom REST layer.&lt;/p&gt;

&lt;p&gt;That matters when you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exact Java Lucene behavior&lt;/li&gt;
&lt;li&gt;complex query or analyzer interactions&lt;/li&gt;
&lt;li&gt;low latency&lt;/li&gt;
&lt;li&gt;fewer translation layers between C# and Java&lt;/li&gt;
&lt;li&gt;a production path that avoids rewrite risk&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A practical decision checklist
&lt;/h2&gt;

&lt;p&gt;Before you pick a path, ask these questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do you need the exact Java Lucene implementation, or just a search engine with similar behavior?&lt;/li&gt;
&lt;li&gt;Is the Java code already a service boundary, or is it a library you want to call directly?&lt;/li&gt;
&lt;li&gt;How often will the .NET side call Lucene?&lt;/li&gt;
&lt;li&gt;Do you need complex objects, callbacks, or rich API access?&lt;/li&gt;
&lt;li&gt;What is the cost of a mismatch if Lucene.NET behaves differently?&lt;/li&gt;
&lt;li&gt;How much deployment overhead can your team tolerate?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those answers usually make the decision obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  My short recommendation
&lt;/h2&gt;

&lt;p&gt;If you can stay on Lucene.NET without losing critical behavior, that can be the simplest path.&lt;/p&gt;

&lt;p&gt;If the Java Lucene code already lives behind a service boundary, REST may be good enough.&lt;/p&gt;

&lt;p&gt;But if the .NET application needs the real Java Lucene library and you want to avoid rewrite risk, JNBridgePro is usually the strongest option.&lt;/p&gt;

&lt;p&gt;Canonical source: &lt;a href="https://jnbridge.com/jnbridgepro/use-java-libraries-in-dotnet-core" rel="noopener noreferrer"&gt;How to Use Java Libraries in .NET Core/.NET 8/9&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>lucene</category>
      <category>search</category>
    </item>
    <item>
      <title>C# J2EE Support: Connecting .NET Applications to Legacy Java Enterprise Systems</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Thu, 06 Aug 2026 13:24:29 +0000</pubDate>
      <link>https://dev.to/jnbridge/c-j2ee-support-connecting-net-applications-to-legacy-java-enterprise-systems-3p8j</link>
      <guid>https://dev.to/jnbridge/c-j2ee-support-connecting-net-applications-to-legacy-java-enterprise-systems-3p8j</guid>
      <description>&lt;p&gt;If you’re a .NET team staring at a Java enterprise stack that refuses to go away, you’re not alone. A lot of migration work is really integration work in disguise: a new C# front end, an ASP.NET Core portal, or a cloud modernization effort that still depends on old J2EE business logic, EJBs, JMS queues, or app-server code that has already survived every rewrite attempt.&lt;/p&gt;

&lt;p&gt;That is why “C# J2EE support” is still a real architecture problem in 2026. The question is not whether Java enterprise systems are legacy. The question is how to connect to them without turning stable business logic into a risky rewrite project.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally published on the &lt;a href="https://jnbridge.com/jnbridgepro/csharp-dotnet-j2ee-support" rel="noopener noreferrer"&gt;JNBridge blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What C# J2EE support means
&lt;/h2&gt;

&lt;p&gt;C# J2EE support is the ability for .NET applications to interoperate with Java enterprise systems, including legacy J2EE apps, Java EE services, application-server components, and Java business logic.&lt;/p&gt;

&lt;p&gt;In plain English:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your business logic still lives in Java&lt;/li&gt;
&lt;li&gt;your new application is written in C#&lt;/li&gt;
&lt;li&gt;the two sides need to talk without forcing a rewrite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That old J2EE name still shows up in real systems because enterprises rarely replace everything at once. Banks, insurers, logistics companies, healthcare vendors, manufacturers, and government systems often still depend on Java enterprise code that has been hardened in production for years.&lt;/p&gt;

&lt;p&gt;If the Java side owns important business behavior, the safest path is usually to reuse it first and modernize around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why .NET teams still need J2EE integration
&lt;/h2&gt;

&lt;p&gt;A .NET J2EE integration project usually starts with a business constraint, not a technology preference.&lt;/p&gt;

&lt;p&gt;Typical scenarios include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a new ASP.NET Core portal calling into Java business rules&lt;/li&gt;
&lt;li&gt;a C# desktop app needing access to Java libraries&lt;/li&gt;
&lt;li&gt;a cloud migration where the Java app server stays in place for now&lt;/li&gt;
&lt;li&gt;a modernization project where the Java workflow is still the source of truth&lt;/li&gt;
&lt;li&gt;a vendor SDK distributed only as Java classes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The temptation is to rewrite the Java side in C#. On paper, that looks clean. In practice, it often means recreating behavior the business already depends on, retesting edge cases, and rediscovering years of hidden assumptions.&lt;/p&gt;

&lt;p&gt;That is why many teams choose integration first. Keep the Java system working. Let the .NET side call into it. Then decide later whether any piece is worth rewriting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common ways to connect C# and J2EE
&lt;/h2&gt;

&lt;p&gt;There is no single right answer. The best option depends on how much of the Java object model the .NET side needs, how fast the calls must be, and who owns the Java runtime.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Best fit&lt;/th&gt;
&lt;th&gt;Strengths&lt;/th&gt;
&lt;th&gt;Tradeoffs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;REST API wrapper&lt;/td&gt;
&lt;td&gt;Coarse service calls&lt;/td&gt;
&lt;td&gt;Simple, language-neutral, easy to monitor&lt;/td&gt;
&lt;td&gt;Requires Java-side API work; may hide rich object behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SOAP / legacy web services&lt;/td&gt;
&lt;td&gt;Existing enterprise Java services&lt;/td&gt;
&lt;td&gt;Often already present in J2EE systems&lt;/td&gt;
&lt;td&gt;Verbose contracts, older tooling, slower evolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Messaging / JMS bridge&lt;/td&gt;
&lt;td&gt;Asynchronous workflows&lt;/td&gt;
&lt;td&gt;Durable, scalable, good for long-running processes&lt;/td&gt;
&lt;td&gt;Not ideal for immediate request-response logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database-level integration&lt;/td&gt;
&lt;td&gt;Reporting or simple handoff&lt;/td&gt;
&lt;td&gt;Minimal app changes in some cases&lt;/td&gt;
&lt;td&gt;Tight coupling to schema; bypasses business logic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java/.NET bridge&lt;/td&gt;
&lt;td&gt;Direct Java class or library reuse&lt;/td&gt;
&lt;td&gt;Preserves Java behavior and object model&lt;/td&gt;
&lt;td&gt;Requires bridge runtime planning and deployment discipline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full rewrite&lt;/td&gt;
&lt;td&gt;Retiring the Java system&lt;/td&gt;
&lt;td&gt;One stack after completion&lt;/td&gt;
&lt;td&gt;High cost, high risk, heavy testing burden&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Many real systems end up using more than one pattern. A .NET app might use REST for one Java service, messaging for background jobs, and a bridge for a complicated Java library that would be painful to wrap by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  When APIs are enough
&lt;/h2&gt;

&lt;p&gt;If the J2EE system already exposes stable service interfaces and your .NET application only needs high-level operations, an API boundary can be the right choice.&lt;/p&gt;

&lt;p&gt;APIs work best when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Java team owns and maintains the service&lt;/li&gt;
&lt;li&gt;calls are coarse-grained and business-oriented&lt;/li&gt;
&lt;li&gt;independent scaling matters&lt;/li&gt;
&lt;li&gt;network latency is acceptable&lt;/li&gt;
&lt;li&gt;the .NET side does not need the full Java object model&lt;/li&gt;
&lt;li&gt;monitoring, authentication, and versioning are already in place&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is often the right answer when the Java app server is already treated as a service boundary.&lt;/p&gt;

&lt;p&gt;But APIs become less attractive when the .NET side needs many fine-grained calls, complex object interaction, callback behavior, or access to Java libraries that were never designed as services.&lt;/p&gt;

&lt;h2&gt;
  
  
  When bridging is better than wrapping everything
&lt;/h2&gt;

&lt;p&gt;Java/.NET bridging becomes interesting when the Java asset is not just a remote application, but a library, SDK, rules engine, or enterprise component that .NET developers need to call directly.&lt;/p&gt;

&lt;p&gt;A bridge can make sense when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Java API surface is large&lt;/li&gt;
&lt;li&gt;object identity or object graphs matter&lt;/li&gt;
&lt;li&gt;the Java code is stable and trusted&lt;/li&gt;
&lt;li&gt;rewriting would create business risk&lt;/li&gt;
&lt;li&gt;hand-writing a REST wrapper for every method would be wasteful&lt;/li&gt;
&lt;li&gt;the .NET app needs lower-latency or more natural calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JNBridgePro supports this by generating proxies between the runtimes. Instead of manually translating every Java class into a custom endpoint, .NET code can call Java functionality through generated .NET-facing proxies while the Java code continues to run as Java.&lt;/p&gt;

&lt;p&gt;That is especially useful for C# J2EE support scenarios where the business logic is packaged in Java classes or enterprise libraries that can be invoked outside the original UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical architecture checklist
&lt;/h2&gt;

&lt;p&gt;Before choosing an integration pattern, map the system honestly. Legacy enterprise Java systems often hide more assumptions than the documentation suggests.&lt;/p&gt;

&lt;p&gt;Ask these questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What is the real unit of reuse: a Java class, business service, EJB, batch job, app-server endpoint, database procedure, or vendor JAR?&lt;/li&gt;
&lt;li&gt;Are the calls coarse business operations or many fine-grained method calls?&lt;/li&gt;
&lt;li&gt;Who controls the Java runtime, app server, JDK version, and configuration?&lt;/li&gt;
&lt;li&gt;Does the Java code assume sessions, transactions, thread-local context, or container-managed resources?&lt;/li&gt;
&lt;li&gt;What happens if the Java side is unavailable, slow, or returns partial results?&lt;/li&gt;
&lt;li&gt;How are credentials, authorization, audit logging, and data boundaries handled?&lt;/li&gt;
&lt;li&gt;Is the Java code strategic, or is it something you can safely retire later?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those questions matter because an architecture is only good if it is supportable in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: a modern .NET app with an existing J2EE rules engine
&lt;/h2&gt;

&lt;p&gt;Imagine an insurance company building a new C# portal for brokers. The portal needs fast access to rating rules that already live inside a Java enterprise system. Those rules have been audited, tested, and refined over years.&lt;/p&gt;

&lt;p&gt;A rewrite would require the team to reproduce every rule in C#, revalidate edge cases, and run parallel testing for months. A database shortcut would bypass business logic. A REST wrapper might work if the rating operation is coarse and stable.&lt;/p&gt;

&lt;p&gt;But if the portal needs direct access to a Java rules library with rich objects and multiple call paths, bridging may be cleaner.&lt;/p&gt;

&lt;p&gt;In a bridged design, the C# portal can call Java rating classes through generated proxies. The Java code remains the source of truth. The .NET team can build the new user experience without duplicating the enterprise logic.&lt;/p&gt;

&lt;p&gt;That is the kind of integration decision that keeps modernization moving without forcing the business into a rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;C# J2EE support is not just about connecting two languages. It is about preserving working enterprise logic while modernizing the front end, the deployment model, or the cloud strategy around it.&lt;/p&gt;

&lt;p&gt;If the Java side is already exposed as a service, APIs may be enough. If the .NET side needs direct access to Java classes, libraries, or object graphs, a Java/.NET bridge is often the better fit.&lt;/p&gt;

&lt;p&gt;The goal is simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep the Java system stable&lt;/li&gt;
&lt;li&gt;let the .NET application move forward&lt;/li&gt;
&lt;li&gt;choose the integration pattern that fits the real boundary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Canonical source: &lt;a href="https://jnbridge.com/jnbridgepro/csharp-dotnet-j2ee-support" rel="noopener noreferrer"&gt;C# J2EE Support: Connecting .NET Applications to Legacy Java Enterprise Systems&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>interop</category>
    </item>
    <item>
      <title>How to Connect Java to C# and Run .NET Libraries from Java</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Mon, 03 Aug 2026 13:25:29 +0000</pubDate>
      <link>https://dev.to/jnbridge/how-to-connect-java-to-c-and-run-net-libraries-from-java-j32</link>
      <guid>https://dev.to/jnbridge/how-to-connect-java-to-c-and-run-net-libraries-from-java-j32</guid>
      <description>&lt;p&gt;If most "Java to C#" articles seem to assume the direction is always C# calling Java, this one flips the problem around.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reverse interop problem
&lt;/h2&gt;

&lt;p&gt;Java and .NET are both mature managed runtimes, but they do not share memory, type systems, or calling conventions.&lt;/p&gt;

&lt;p&gt;That means the answer to "how do I use a C# library from Java?" is never just &lt;code&gt;import&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Typical examples include:&lt;/p&gt;

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

&lt;p&gt;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?&lt;/p&gt;

&lt;h2&gt;
  
  
  Four practical ways to connect Java to C
&lt;/h2&gt;

&lt;p&gt;There are really four broad options.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Proxy-based bridging
&lt;/h3&gt;

&lt;p&gt;This is the closest thing to "native" interoperability.&lt;/p&gt;

&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Generated proxy for a .NET class&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mycompany&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dotnet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PdfRenderer&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;render&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"template.html"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;Files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"output.pdf"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;pdf&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Java developer experiences a normal object model. The bridge handles CLR startup, type marshalling, and exception translation.&lt;/p&gt;

&lt;p&gt;Use this when you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;close-grained calls&lt;/li&gt;
&lt;li&gt;object-heavy APIs&lt;/li&gt;
&lt;li&gt;shared business logic that should not be wrapped in HTTP&lt;/li&gt;
&lt;li&gt;a real need to reuse .NET code in-process&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. REST or gRPC
&lt;/h3&gt;

&lt;p&gt;If the boundary is naturally service-oriented, this is often the cleanest answer.&lt;/p&gt;

&lt;p&gt;Wrap the .NET component in an ASP.NET Core API or a gRPC service, then call it from Java.&lt;/p&gt;

&lt;p&gt;This works well when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the .NET component is already a service&lt;/li&gt;
&lt;li&gt;the call pattern is coarse-grained&lt;/li&gt;
&lt;li&gt;latency is acceptable&lt;/li&gt;
&lt;li&gt;the Java and .NET pieces will remain independently deployed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tradeoff is obvious: you gain architectural clarity, but you also pay for serialization, network hops, and another runtime to operate.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. COM interop
&lt;/h3&gt;

&lt;p&gt;COM still exists in a lot of enterprises, especially in Windows-only environments.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The downside is just as obvious: it is Windows-only, and it is not the best long-term modernization path.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Native hosting via coreclr
&lt;/h3&gt;

&lt;p&gt;In some cases, teams try to host the CLR natively from the Java side or build custom loaders around the runtime.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;If the use case is enterprise production software, a supported bridge is usually the safer choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why bridging usually beats rewriting
&lt;/h2&gt;

&lt;p&gt;Rewriting sounds neat on slides because it promises purity.&lt;/p&gt;

&lt;p&gt;In practice, rewrites usually force teams to rebuild:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;business rules that already work&lt;/li&gt;
&lt;li&gt;integration behavior discovered only in production&lt;/li&gt;
&lt;li&gt;test coverage accumulated over years&lt;/li&gt;
&lt;li&gt;edge cases nobody fully documented&lt;/li&gt;
&lt;li&gt;deployment and rollback behavior that production already depends on&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  How to decide which path fits
&lt;/h2&gt;

&lt;p&gt;Use this simple decision rule:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;If your integration is one request in, one response out, the service boundary is probably fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run a C# library from Java in production
&lt;/h2&gt;

&lt;p&gt;The production checklist is more important than the demo.&lt;/p&gt;

&lt;p&gt;When you load .NET DLLs in Java, make sure you have a plan for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runtime version compatibility&lt;/li&gt;
&lt;li&gt;exception translation&lt;/li&gt;
&lt;li&gt;thread ownership and apartment models&lt;/li&gt;
&lt;li&gt;startup latency&lt;/li&gt;
&lt;li&gt;classpath and assembly loading&lt;/li&gt;
&lt;li&gt;logging across both runtimes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best bridge in the world does not help if the surrounding operational model is brittle.&lt;/p&gt;

&lt;h2&gt;
  
  
  A final rule of thumb
&lt;/h2&gt;

&lt;p&gt;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?"&lt;/p&gt;

&lt;p&gt;The first question should be "how do we let Java use it safely?"&lt;/p&gt;

&lt;p&gt;That is the real problem this article solves.&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>interop</category>
    </item>
    <item>
      <title>C# JVM and JDK Integration: How .NET Teams Can Use Java Without Rewriting</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Fri, 31 Jul 2026 13:21:44 +0000</pubDate>
      <link>https://dev.to/jnbridge/c-jvm-and-jdk-integration-how-net-teams-can-use-java-without-rewriting-29mg</link>
      <guid>https://dev.to/jnbridge/c-jvm-and-jdk-integration-how-net-teams-can-use-java-without-rewriting-29mg</guid>
      <description>&lt;p&gt;If you’ve ever searched for “C# JVM” or “C# JDK integration,” you already know the real problem: your .NET team needs to use Java code, but the answer is not another rewrite project. The useful question is how to connect the two runtimes in a way that keeps production systems stable while the business keeps moving.&lt;/p&gt;

&lt;p&gt;Originally published on the &lt;a href="https://jnbridge.com/jnbridgepro/csharp-jvm-jdk-integration" rel="noopener noreferrer"&gt;JNBridge blog&lt;/a&gt;, this guide maps the Java platform back to the .NET world and shows the practical integration options that actually hold up in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  What People Usually Mean by “C# JVM”
&lt;/h2&gt;

&lt;p&gt;Strictly speaking, C# does not run on the JVM in mainstream .NET deployments. What people usually mean is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a .NET application needs to call Java classes or JARs&lt;/li&gt;
&lt;li&gt;a Java system needs to serve a C# or .NET application&lt;/li&gt;
&lt;li&gt;an existing Java dependency should be reused instead of rewritten&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why “C# JVM integration” is really about interoperability between two runtimes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.NET / CLR&lt;/strong&gt; on the Microsoft side&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JVM / JDK&lt;/strong&gt; on the Java side&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your team understands that distinction, the rest of the architecture choices get much easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  JVM vs. CLR: The Mental Model
&lt;/h2&gt;

&lt;p&gt;The JVM and CLR are both managed runtimes. They compile code, manage memory, and handle execution at runtime. The analogy is helpful:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Java&lt;/th&gt;
&lt;th&gt;.NET&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JVM&lt;/td&gt;
&lt;td&gt;CLR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JDK&lt;/td&gt;
&lt;td&gt;.NET SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JAR&lt;/td&gt;
&lt;td&gt;Assembly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;javac&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;dotnet build&lt;/code&gt; / Roslyn&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key difference is not just language. It’s operational ownership. If your Java code is already stable, rewriting it in C# can introduce new defects, new timelines, and new support burdens for very little payoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Integration Options That Actually Exist
&lt;/h2&gt;

&lt;p&gt;There are really five broad ways to connect .NET and Java:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. REST APIs
&lt;/h3&gt;

&lt;p&gt;Put Java behind an HTTP service and call it from .NET. This is the most familiar approach, and it’s great when the boundary is already service-oriented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; distributed systems, coarse-grained calls, clear ownership boundaries&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tradeoff:&lt;/strong&gt; serialization overhead and higher latency&lt;/p&gt;

&lt;h3&gt;
  
  
  2. gRPC
&lt;/h3&gt;

&lt;p&gt;gRPC gives you binary payloads and strong contracts. If you already want service boundaries, it can be a big step up from ad hoc JSON endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; service-to-service communication&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tradeoff:&lt;/strong&gt; still a network hop, still a separate service to run&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Translation / Recompilation
&lt;/h3&gt;

&lt;p&gt;Tools like IKVM take a different path: convert Java bytecode into .NET assemblies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; limited Java 8-era codebases with simple dependencies&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tradeoff:&lt;/strong&gt; modern Java support is the hard part, and supportability tends to become the issue later&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Native Library Boundaries
&lt;/h3&gt;

&lt;p&gt;If you can turn the Java side into a native artifact or wrap it another way, you can call across a lower-level boundary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; specialized cases&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tradeoff:&lt;/strong&gt; complexity goes up fast, especially when reflection and dynamic loading are involved&lt;/p&gt;

&lt;h3&gt;
  
  
  5. In-Process Bridging
&lt;/h3&gt;

&lt;p&gt;This is the option most teams actually want once they stop chasing rewrite fantasies: keep Java running on a real JVM, keep .NET on the CLR, and connect them with a purpose-built bridge.&lt;/p&gt;

&lt;p&gt;That lets .NET call Java with typed proxies instead of hand-rolled HTTP wrappers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Bridging Beats Rewriting
&lt;/h2&gt;

&lt;p&gt;Rewriting sounds cleaner than it is. In practice, rewrites usually force teams to rebuild:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;business rules that already work&lt;/li&gt;
&lt;li&gt;edge cases that only show up in production&lt;/li&gt;
&lt;li&gt;integration contracts with surrounding systems&lt;/li&gt;
&lt;li&gt;test coverage accumulated over years&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cost is not just engineering time. It’s also the risk of changing behavior you don’t fully understand yet.&lt;/p&gt;

&lt;p&gt;If the Java code is already producing value, the better move is often to &lt;strong&gt;bridge first and modernize later&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When JNBridgePro Fits
&lt;/h2&gt;

&lt;p&gt;JNBridgePro is built for teams that need direct Java/.NET interoperability without forcing a rewrite.&lt;/p&gt;

&lt;p&gt;That matters when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;your Java dependencies are too valuable to replace&lt;/li&gt;
&lt;li&gt;your .NET app needs to call Java APIs directly&lt;/li&gt;
&lt;li&gt;you want a real JVM, not a translation layer&lt;/li&gt;
&lt;li&gt;you need production support for modern Java and .NET runtimes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s a good fit for enterprise teams that care more about delivery risk than theoretical purity.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Simple Decision Rule
&lt;/h2&gt;

&lt;p&gt;Use this rule of thumb:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;choose &lt;strong&gt;REST or gRPC&lt;/strong&gt; if you already want a service boundary&lt;/li&gt;
&lt;li&gt;choose &lt;strong&gt;translation&lt;/strong&gt; only if your Java footprint is small and simple&lt;/li&gt;
&lt;li&gt;choose &lt;strong&gt;bridging&lt;/strong&gt; if you need Java and .NET to cooperate closely in production&lt;/li&gt;
&lt;li&gt;choose a &lt;strong&gt;rewrite&lt;/strong&gt; only when the old system is actually disposable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters. A lot of systems are old. Very few are disposable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;If you’re asking about C# JVM integration, you usually don’t need a language migration. You need a reliable way to keep Java and .NET working together while avoiding the cost and risk of a rewrite.&lt;/p&gt;

&lt;p&gt;That’s the problem this article is really about.&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>interop</category>
    </item>
    <item>
      <title>Modernize Legacy .NET Without a Full Rewrite: A Practical Roadmap</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Mon, 11 May 2026 13:25:49 +0000</pubDate>
      <link>https://dev.to/jnbridge/modernize-legacy-net-without-a-full-rewrite-a-practical-roadmap-3pca</link>
      <guid>https://dev.to/jnbridge/modernize-legacy-net-without-a-full-rewrite-a-practical-roadmap-3pca</guid>
      <description>&lt;p&gt;Legacy .NET systems usually aren't "old code" so much as years of production rules, edge cases, integrations, and compliance work that the business still depends on. The safest modernization plan is often not a rewrite — it's a staged path that keeps the working system alive while you peel off risk one piece at a time.&lt;/p&gt;

&lt;p&gt;This is a developer-to-architect walkthrough of how to choose between infrastructure upgrades, APIs, strangler-fig replacement, and direct application bridging when a full rewrite is too risky.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally published on the &lt;a href="https://jnbridge.com/jnbridgepro/modernize-legacy-dotnet-application-without-rewrite" rel="noopener noreferrer"&gt;JNBridge blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Legacy .NET Modernization Challenge
&lt;/h2&gt;

&lt;p&gt;Enterprise .NET applications often contain &lt;strong&gt;10-20 years of accumulated business logic&lt;/strong&gt;, regulatory compliance features, and integration points that represent millions of dollars in development investment. These systems face mounting pressure from multiple directions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloud Migration Requirements:&lt;/strong&gt; Organizations need applications that can deploy to Azure, AWS, or hybrid environments without extensive infrastructure dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration Demands:&lt;/strong&gt; Modern business requires seamless data exchange with SaaS applications, mobile systems, and third-party APIs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Experience Problems:&lt;/strong&gt; Recruitment and retention suffer when technology teams work exclusively with outdated frameworks and deployment practices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security and Compliance Updates:&lt;/strong&gt; Regulatory requirements and security standards evolve faster than monolithic application update cycles can accommodate.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Hidden Value in Legacy .NET Systems
&lt;/h3&gt;

&lt;p&gt;Before considering any modernization approach, &lt;strong&gt;recognize what your legacy .NET application does exceptionally well&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Battle-tested business logic&lt;/strong&gt; refined through years of production use&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deep integration&lt;/strong&gt; with Windows-based enterprise systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance-optimized code&lt;/strong&gt; for specific business processes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compliance-certified workflows&lt;/strong&gt; that meet regulatory requirements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extensive configuration options&lt;/strong&gt; that support diverse business scenarios&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal of .NET modernization without rewrite is &lt;strong&gt;enhancing these strengths rather than replacing them&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Full Rewrites Fail for .NET Applications
&lt;/h2&gt;

&lt;p&gt;Independent analysis of 1,471 IT projects found heavy tail risk in large transformations, including significant black-swan overruns (&lt;a href="https://arxiv.org/abs/1304.0265" rel="noopener noreferrer"&gt;source&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The track record for complete .NET application rewrites is sobering.&lt;/strong&gt; Research indicates that 68% of enterprise application rewrite projects exceed their original timeline by more than 12 months, with 31% ultimately failing to deliver functional replacements.&lt;/p&gt;

&lt;h3&gt;
  
  
  The .NET Framework Dependency Web
&lt;/h3&gt;

&lt;p&gt;Legacy .NET applications rarely exist in isolation. They typically depend on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shared libraries&lt;/strong&gt; developed over multiple years&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;COM components&lt;/strong&gt; that interface with specialized hardware or legacy systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Third-party controls&lt;/strong&gt; that may not have modern equivalents&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Database stored procedures&lt;/strong&gt; containing complex business rules&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Windows-specific services&lt;/strong&gt; for background processing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rewriting means recreating not just the application but &lt;strong&gt;its entire ecosystem of dependencies&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Business Logic Archeology Problem
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Enterprise .NET applications contain embedded knowledge&lt;/strong&gt; that exists nowhere else in the organization:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Undocumented business rules implemented in edge case handling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Integration logic for partner systems that lack current documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Performance optimizations developed through years of production tuning&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Regulatory compliance features implemented by consultants who are no longer available&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This “tribal knowledge” cannot be captured in requirements documents or reverse-engineered from user interfaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Parallel Development Trap
&lt;/h3&gt;

&lt;p&gt;Full rewrites require &lt;strong&gt;maintaining two versions&lt;/strong&gt; of your application simultaneously:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Legacy system maintenance&lt;/strong&gt; – bug fixes, regulatory updates, new business requirements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Replacement system development&lt;/strong&gt; – building modern equivalent functionality&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most organizations underestimate the cost and complexity of this parallel development period, which typically extends 18-36 months longer than projected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Complete Spectrum of .NET Modernization Approaches
&lt;/h2&gt;

&lt;p&gt;Use a portfolio strategy, not a one-size-fits-all plan: Microsoft’s Cloud Adoption Framework maps retire/rehost/replatform/refactor/rearchitect/rebuild/replace decisions (&lt;a href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/plan/select-cloud-migration-strategy" rel="noopener noreferrer"&gt;reference&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Successful .NET modernization without rewrite requires choosing the right strategy&lt;/strong&gt; for your specific situation. Here are five proven approaches, ranked from lowest to highest business disruption:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Infrastructure Modernization Only
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scope:&lt;/strong&gt; Upgrade hosting environment while preserving application code &lt;strong&gt;Timeline:&lt;/strong&gt; 2-6 months &lt;strong&gt;Risk Level:&lt;/strong&gt; Low &lt;strong&gt;Business Impact:&lt;/strong&gt; Minimal&lt;/p&gt;

&lt;p&gt;This approach moves existing .NET Framework applications to modern hosting environments (containers, cloud platforms) without changing application code. It delivers immediate operational benefits while preserving all existing functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Selective Component Updates
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scope:&lt;/strong&gt; Modernize specific application components while maintaining core system &lt;strong&gt;Timeline:&lt;/strong&gt; 3-9 months &lt;strong&gt;Risk Level:&lt;/strong&gt; Low-Medium &lt;strong&gt;Business Impact:&lt;/strong&gt; Limited&lt;/p&gt;

&lt;p&gt;Replace or upgrade individual components (user interfaces, reporting modules, integration layers) while preserving core business logic. This enables targeted improvements without system-wide changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. API-First Modernization
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scope:&lt;/strong&gt; Expose legacy functionality through modern APIs &lt;strong&gt;Timeline:&lt;/strong&gt; 6-12 months &lt;strong&gt;Risk Level:&lt;/strong&gt; Medium &lt;strong&gt;Business Impact:&lt;/strong&gt; Moderate&lt;/p&gt;

&lt;p&gt;Wrap existing .NET applications with RESTful APIs or GraphQL interfaces, enabling modern applications to consume legacy business logic without direct system integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Hybrid Architecture Implementation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scope:&lt;/strong&gt; Integrate legacy and modern applications through bridging technology &lt;strong&gt;Timeline:&lt;/strong&gt; 3-8 months &lt;strong&gt;Risk Level:&lt;/strong&gt; Medium &lt;strong&gt;Business Impact:&lt;/strong&gt; Low-Medium&lt;/p&gt;

&lt;p&gt;Enable direct communication between legacy .NET applications and modern systems without requiring changes to existing code. This approach leverages &lt;a href="https://jnbridge.com/jnbridgepro/java-dotnet-integration-options-compared" rel="noopener noreferrer"&gt;interoperability solutions&lt;/a&gt; to bridge technology gaps.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Incremental Component Replacement
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scope:&lt;/strong&gt; Gradually replace legacy components with modern equivalents &lt;strong&gt;Timeline:&lt;/strong&gt; 12-24 months &lt;strong&gt;Risk Level:&lt;/strong&gt; High &lt;strong&gt;Business Impact:&lt;/strong&gt; High&lt;/p&gt;

&lt;p&gt;Systematically replace application components over time using patterns like the strangler fig approach. This provides the benefits of modern architecture while managing implementation risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes Incremental .NET Modernization Work?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The most successful .NET modernization projects share common characteristics&lt;/strong&gt; that distinguish them from failed rewrite attempts:&lt;/p&gt;

&lt;h3&gt;
  
  
  Preservation of Business Logic Integrity
&lt;/h3&gt;

&lt;p&gt;Incremental approaches &lt;strong&gt;maintain existing business rules and workflows&lt;/strong&gt; while modernizing the technology foundation. This eliminates the risk of losing embedded business knowledge or introducing functional regressions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuous Value Delivery
&lt;/h3&gt;

&lt;p&gt;Rather than waiting 18-24 months for big-bang deployment, incremental modernization &lt;strong&gt;delivers benefits in 3-6 month cycles&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Improved deployment capabilities&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enhanced integration options&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Modern development tool support&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cloud hosting flexibility&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Performance optimizations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Risk Mitigation Through Reversibility
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Every modernization step remains reversible&lt;/strong&gt; until proven successful in production. If new components fail or perform poorly, organizations can quickly revert to previous configurations without business impact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Team Learning and Skill Development
&lt;/h3&gt;

&lt;p&gt;Incremental projects allow development teams to &lt;strong&gt;gradually acquire modern .NET skills&lt;/strong&gt; while maintaining productivity with existing systems. This eliminates the knowledge gap that often derails rewrite projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Strangler Fig Pattern for .NET Applications
&lt;/h2&gt;

&lt;p&gt;Major platform guidance supports phased modernization to reduce disruption and migration risk (&lt;a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig" rel="noopener noreferrer"&gt;Microsoft Strangler Fig&lt;/a&gt;, AWS Strangler Fig).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The strangler fig pattern offers a particularly effective approach&lt;/strong&gt; for legacy .NET modernization. Named after the vine that gradually encompasses and replaces host trees, this pattern incrementally replaces legacy components while maintaining system functionality.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Strangler Fig Works with .NET Applications
&lt;/h3&gt;

&lt;p&gt;The pattern operates through three phases:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1: Interception Layer&lt;/strong&gt; Create a routing layer that can direct requests to either legacy or modern components. For .NET applications, this often involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;API gateways for web requests&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Service abstractions for business logic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Database abstraction layers for data access&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: Incremental Replacement&lt;/strong&gt; Systematically replace individual components while routing production traffic through the interception layer. The &lt;a href="https://jnbridge.com/jnbridgepro/strangler-fig-pattern-migrate-java-monolith-to-dotnet" rel="noopener noreferrer"&gt;strangler fig pattern implementation guide&lt;/a&gt; provides detailed technical approaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 3: Legacy System Retirement&lt;/strong&gt; Once all components are replaced and validated, decommission the original legacy system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strangler Fig Benefits for .NET Modernization
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Reduced Business Risk:&lt;/strong&gt; New components undergo production validation before taking full responsibility for business processes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Continuous Operation:&lt;/strong&gt; Legacy systems continue serving users while modernization proceeds in parallel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexible Timeline:&lt;/strong&gt; Organizations can adjust modernization pace based on business priorities and resource availability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investment Protection:&lt;/strong&gt; Existing .NET investments continue generating value throughout the modernization process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing Modernization Timelines and Risk Levels
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Understanding the time and risk implications&lt;/strong&gt; of different approaches helps organizations make informed modernization decisions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Timeline&lt;/th&gt;
&lt;th&gt;Risk level&lt;/th&gt;
&lt;th&gt;Business disruption&lt;/th&gt;
&lt;th&gt;Technical debt reduction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;| Infrastructure only | 2–6 months | Very low | Minimal | Low |&lt;/p&gt;

&lt;p&gt;| Component updates | 3–9 months | Low | Limited | Medium |&lt;/p&gt;

&lt;p&gt;| API-first | 6–12 months | Medium | Moderate | Medium |&lt;/p&gt;

&lt;p&gt;| Hybrid architecture | 3–8 months | Medium | Low | High |&lt;/p&gt;

&lt;p&gt;| Strangler fig | 12–24 months | High | Variable | Very high |&lt;/p&gt;

&lt;p&gt;| Complete rewrite | 18–48 months | Very high | Significant | Maximum |&lt;/p&gt;

&lt;h3&gt;
  
  
  Timeline Reality Check
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Actual modernization timelines consistently exceed estimates&lt;/strong&gt; when organizations fail to account for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Integration complexity&lt;/strong&gt; with existing enterprise systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data migration challenges&lt;/strong&gt; between different architectural patterns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User acceptance testing&lt;/strong&gt; requirements for business-critical workflows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Change management&lt;/strong&gt; across multiple business units&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance optimization&lt;/strong&gt; to match legacy system response times&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Risk Mitigation Strategies
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Organizations that successfully modernize legacy .NET applications&lt;/strong&gt; implement these risk reduction practices:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallel Operation Periods:&lt;/strong&gt; Run legacy and modern components simultaneously until new systems prove reliable in production environments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Comprehensive Testing Frameworks:&lt;/strong&gt; Develop automated testing that validates business logic consistency across old and new implementations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rollback Procedures:&lt;/strong&gt; Maintain documented procedures for quickly reverting to previous configurations if modernization components fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance Benchmarking:&lt;/strong&gt; Establish baseline performance metrics and monitor closely during modernization to prevent user experience degradation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application Bridging: The Proven Alternative to Rewrites
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Application bridging emerges as the most pragmatic modernization strategy&lt;/strong&gt; for organizations seeking immediate benefits without rewrite risks. This approach enables legacy .NET applications to integrate seamlessly with modern systems while preserving existing business logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Application Bridging Works
&lt;/h3&gt;

&lt;p&gt;Modern bridging technology creates &lt;strong&gt;direct communication channels&lt;/strong&gt; between .NET applications and other technology stacks. For example, legacy .NET Framework applications can directly invoke methods in modern Java services, .NET Core applications, or cloud-based APIs without requiring architectural changes.&lt;/p&gt;

&lt;p&gt;Key capabilities include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Native method invocation&lt;/strong&gt; across different runtime environments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shared object models&lt;/strong&gt; that work across technology boundaries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Exception handling&lt;/strong&gt; that propagates correctly between systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance optimization&lt;/strong&gt; that minimizes integration overhead&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bridging vs. Traditional Integration Approaches
&lt;/h3&gt;

&lt;p&gt;Unlike REST APIs, message queues, or file-based integration, &lt;strong&gt;bridging provides native-level connectivity&lt;/strong&gt; between applications:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance Advantages:&lt;/strong&gt; Direct method calls eliminate HTTP overhead and serialization costs, often improving performance by 40-70% compared to REST-based integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development Simplicity:&lt;/strong&gt; Developers work with familiar object models and method signatures rather than learning new integration protocols.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maintenance Reduction:&lt;/strong&gt; Single bridging configuration replaces multiple integration points, reducing operational complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type Safety:&lt;/strong&gt; Compile-time validation catches integration errors before deployment, unlike runtime-only validation in API-based approaches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real-World Bridging Implementation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;A Fortune 500 manufacturing company&lt;/strong&gt; recently modernized their core .NET inventory management system using application bridging rather than a planned $2.8 million rewrite:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Legacy .NET Framework application needed to integrate with new Java-based supply chain management system and cloud analytics platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bridging Solution:&lt;/strong&gt; Implemented &lt;a href="https://jnbridge.com/software/jnbridgepro/overview" rel="noopener noreferrer"&gt;JNBridge interoperability platform&lt;/a&gt; to enable direct communication between .NET, Java, and cloud components.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Results:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Timeline:&lt;/strong&gt; 4 months vs. 28-month rewrite estimate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cost:&lt;/strong&gt; $180,000 vs. $2.8 million rewrite budget&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Business Impact:&lt;/strong&gt; Zero operational disruption&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance:&lt;/strong&gt; 15% faster than previous REST-based integration&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Strategic Benefits of Bridging for .NET Modernization
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Immediate Integration Value:&lt;/strong&gt; Legacy .NET applications can immediately consume modern services and expose functionality to new systems without code changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Future Architecture Flexibility:&lt;/strong&gt; Bridging preserves all future modernization options while delivering immediate benefits. Organizations can still pursue rewrites, component replacement, or other strategies while bridged systems operate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Investment Protection:&lt;/strong&gt; Existing .NET development investments continue generating value while new capabilities are added through modern applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Team Productivity:&lt;/strong&gt; .NET developers continue working with familiar tools and frameworks while gaining access to modern ecosystem capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Your .NET Modernization Roadmap
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Successful .NET modernization requires a systematic approach&lt;/strong&gt; that balances immediate business needs with long-term architectural goals. Here’s a proven framework for building your modernization strategy:&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Assessment and Planning (Month 1-2)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Legacy System Analysis:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Document current application architecture and dependencies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identify integration points with other enterprise systems&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Catalog business-critical functionality and performance requirements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Assess technical debt and maintenance burden&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Modernization Goal Definition:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cloud deployment requirements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Integration needs with modern applications&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Developer experience improvements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Performance and scalability targets&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Approach Selection:&lt;/strong&gt; Use the decision matrix to select the optimal modernization approach based on your specific constraints and objectives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Proof of Concept (Month 2-3)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Technical Validation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Test chosen modernization approach with non-critical application components&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Validate integration patterns and performance characteristics&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Develop deployment and rollback procedures&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Train development team on new tools and approaches&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Business Case Refinement:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Quantify benefits and costs based on proof-of-concept results&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adjust timeline estimates based on actual implementation experience&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Secure stakeholder buy-in for full implementation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 3: Incremental Implementation (Month 3-12)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Component-by-Component Modernization:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Start with least critical components to minimize business risk&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement comprehensive testing for each modernized component&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Monitor performance and user feedback throughout implementation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Adjust approach based on lessons learned from each component&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Integration Expansion:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Gradually expand integration capabilities as confidence builds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add new business functionality that leverages modernized architecture&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Document patterns and practices for future modernization cycles&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 4: Architecture Evolution (Month 6-18)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Advanced Capabilities:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Implement cloud-native features like auto-scaling and distributed deployment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add modern monitoring, logging, and observability capabilities&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enhance security with modern authentication and authorization patterns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optimize performance using cloud platform capabilities&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Long-term Strategy:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Evaluate options for further modernization or component replacement&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Plan for future technology evolution and business requirements&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Establish ongoing modernization practices for continuous improvement&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Getting Started with .NET Modernization
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ready to modernize your legacy .NET application?&lt;/strong&gt; The most successful projects begin with a clear understanding of current capabilities and specific modernization objectives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jnbridge.com/software/jnbridgepro/overview" rel="noopener noreferrer"&gt;Application bridging with JNBridge&lt;/a&gt; offers the fastest path to .NET modernization benefits without rewrite risks. Their enterprise-proven platform enables immediate integration between legacy .NET applications and modern systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start your modernization journey:&lt;/strong&gt; &lt;a href="https://jnbridge.com/download/download-jnbridgepro" rel="noopener noreferrer"&gt;Download JNBridge Pro&lt;/a&gt; and test bridging capabilities with your existing .NET applications. Most organizations complete their evaluation within 1-2 weeks and begin seeing modernization benefits within 30 days.&lt;/p&gt;

&lt;p&gt;The choice between rewriting and modernizing determines whether your .NET application continues delivering business value or becomes a costly distraction from strategic initiatives. Smart organizations choose modernization approaches that enhance existing investments rather than abandoning them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your legacy .NET application represents years of refined business logic and proven reliability.&lt;/strong&gt; Modernization without rewrite preserves these assets while enabling the contemporary capabilities your organization needs for future growth.&lt;/p&gt;

&lt;p&gt;Learn more about &lt;a href="https://jnbridge.com/jnbridgepro/how-to-call-java-from-csharp" rel="noopener noreferrer"&gt;calling Java from C#&lt;/a&gt; and calling C# from Java to understand how bridging technology enables seamless integration between different technology stacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modernizing a Legacy .NET Application: Practical Checklist
&lt;/h2&gt;

&lt;p&gt;Define latency targets, map object boundaries, establish error handling, and automate integration tests before rollout. Teams that document these four items early reduce production surprises and improve delivery speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modernizing a Legacy .NET Application: Practical Checklist
&lt;/h2&gt;

&lt;p&gt;Define latency targets, map object boundaries, establish error handling, and automate integration tests before rollout. Teams that document these four items early reduce production surprises and improve delivery speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom line
&lt;/h2&gt;

&lt;p&gt;A full rewrite can be the right answer when the domain is small, well understood, and isolated. Enterprise .NET systems rarely fit that profile. If the application contains valuable business logic, opaque dependencies, or regulatory workflows, modernization works better as a sequence of reversible steps: stabilize the platform, expose seams, bridge where direct reuse matters, and replace components only when the replacement has proven itself in production.&lt;/p&gt;

&lt;p&gt;Canonical source: &lt;a href="https://dev.to{source_url}"&gt;How to Modernize a Legacy .NET Application Without a Full Rewrite&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>interop</category>
    </item>
    <item>
      <title>Run Java from C#: 5 Methods with Code Examples</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Thu, 07 May 2026 14:38:37 +0000</pubDate>
      <link>https://dev.to/jnbridge/run-java-from-c-5-methods-with-code-examples-2g6h</link>
      <guid>https://dev.to/jnbridge/run-java-from-c-5-methods-with-code-examples-2g6h</guid>
      <description>&lt;p&gt;If you need to call Java from a .NET app, the first answer is usually “just shell out to &lt;code&gt;java.exe&lt;/code&gt;.” That works right up until you need objects, exceptions, throughput, or a deployment story that doesn’t become a pile of scripts.&lt;/p&gt;

&lt;p&gt;This Dev.to version walks through five practical ways to run Java from C# — from &lt;code&gt;Process.Start&lt;/code&gt; to IKVM, JNI, gRPC, and in-process bridging — with code and the tradeoffs that matter in production.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Need to &lt;strong&gt;run Java from C#&lt;/strong&gt;? Use &lt;strong&gt;Process.Start&lt;/strong&gt; for one-off JAR executions, &lt;strong&gt;IKVM&lt;/strong&gt; for pure-Java libraries with no native dependencies, &lt;strong&gt;gRPC&lt;/strong&gt; for microservice architectures, &lt;strong&gt;JNI&lt;/strong&gt; if you have C++ expertise and need raw speed, or &lt;strong&gt;&lt;a href="https://jnbridge.com/download/download-jnbridgepro" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt;&lt;/strong&gt; for production-grade in-process bridging with low latency and zero JNI glue code. See the comparison table and decision tree below.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You have a Java library you need to use from a C#/.NET application — maybe a payment SDK, a machine-learning model, or a legacy system nobody wants to rewrite. Whatever the reason, you need to &lt;strong&gt;run Java from C#&lt;/strong&gt;, and it has to work in production.&lt;/p&gt;

&lt;p&gt;If you’ve searched before, you probably found a StackOverflow answer from 2012 telling you to use &lt;code&gt;Process.Start("java.exe")&lt;/code&gt;. That works for trivial cases, but falls apart when you need &lt;strong&gt;real interop&lt;/strong&gt;: passing objects, handling exceptions across the &lt;strong&gt;JVM&lt;/strong&gt; and &lt;strong&gt;CLR&lt;/strong&gt;, or making thousands of calls per second with minimal &lt;strong&gt;latency&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This guide covers &lt;strong&gt;five real methods to run Java code in C#&lt;/strong&gt;, from simple shell-out to full &lt;strong&gt;in-process&lt;/strong&gt; bridging. Each includes working code, honest trade-offs, and guidance on when to use it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Quick Comparison&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Method 1: Process.Start — Run java.exe as a Subprocess&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Method 2: IKVM — Compile Java Bytecode to .NET&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Method 3: JNI via C++/CLI Wrapper&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Method 4: gRPC Sidecar — Run Java as a Microservice&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Method 5: JNBridgePro — In-Process Java/.NET Bridge&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Performance Benchmarks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which Method Should You Use?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How Do You Handle Java Dependencies from C#?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Can You Run Java from C# Without a JDK?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Frequently Asked Questions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wrapping Up&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Quick Comparison
&lt;/h2&gt;

&lt;p&gt;Before diving into code, here’s what you’re choosing between:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Integration Depth&lt;/th&gt;
&lt;th&gt;Per-Call Latency&lt;/th&gt;
&lt;th&gt;Complexity&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Process.Start&lt;/td&gt;
&lt;td&gt;Shallow (stdin/stdout)&lt;/td&gt;
&lt;td&gt;High (~50ms+)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;One-off JAR execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IKVM&lt;/td&gt;
&lt;td&gt;Deep (.NET assembly)&lt;/td&gt;
&lt;td&gt;Low (~0.1ms)&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Pure-Java libs, no native deps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JNI via C++/CLI&lt;/td&gt;
&lt;td&gt;Deep (native calls)&lt;/td&gt;
&lt;td&gt;Lowest (~0.05ms)&lt;/td&gt;
&lt;td&gt;Very High&lt;/td&gt;
&lt;td&gt;Max control, C++ teams&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gRPC Sidecar&lt;/td&gt;
&lt;td&gt;Medium (RPC)&lt;/td&gt;
&lt;td&gt;Medium (~2–5ms)&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Microservices, cloud-native&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JNBridgePro&lt;/td&gt;
&lt;td&gt;Deep (in-process)&lt;/td&gt;
&lt;td&gt;Low (~0.1ms)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Production apps, bidirectional&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 For a deeper dive on bridge vs. REST vs. gRPC trade-offs, see our &lt;a href="https://jnbridge.com/?p=5270" rel="noopener noreferrer"&gt;Bridge vs REST vs gRPC comparison&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Method 1: Process.Start — Run java.exe as a Subprocess
&lt;/h2&gt;

&lt;p&gt;The most straightforward way to &lt;strong&gt;run Java from C#&lt;/strong&gt; is to launch the &lt;strong&gt;JVM&lt;/strong&gt; as a separate process using &lt;code&gt;Process.Start&lt;/code&gt;. This is what most StackOverflow answers suggest, and for simple, one-shot tasks it’s perfectly fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use It
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Running a standalone Java CLI tool or JAR file&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One-off executions (batch jobs, code generation, file conversion)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You don’t need to pass complex objects back and forth&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Diagnostics&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JavaProcessRunner&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;RunJavaJarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;jarPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;javaHome&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;javaExe&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javaHome&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;

            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;javaHome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"java"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"java"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;startInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ProcessStartInfo&lt;/span&gt;

        &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="n"&gt;FileName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javaExe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

            &lt;span class="n"&gt;Arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"-jar \"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;jarPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\" &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

            &lt;span class="n"&gt;RedirectStandardOutput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

            &lt;span class="n"&gt;RedirectStandardError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

            &lt;span class="n"&gt;UseShellExecute&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

            &lt;span class="n"&gt;CreateNoWindow&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;

        &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Pass classpath and other JVM options via environment&lt;/span&gt;

        &lt;span class="n"&gt;startInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"CLASSPATH"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;

            &lt;span class="s"&gt;"/libs/dependency1.jar:/libs/dependency2.jar"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Process&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;StartInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;startInfo&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutputDataReceived&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;

            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrorDataReceived&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;

            &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginOutputReadLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BeginErrorReadLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CancellationTokenSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

            &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;

        &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitForExitAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OperationCanceledException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entireProcessTree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TimeoutException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

                &lt;span class="s"&gt;"Java process timed out after 30s"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExitCode&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

                &lt;span class="s"&gt;$"Java exited with code &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExitCode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;JavaProcessRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunJavaJarAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

    &lt;span class="s"&gt;"/app/libs/converter.jar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="s"&gt;"--input data.csv --format json"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🔗 For a complete walkthrough of running JAR files from .NET, see &lt;a href="https://jnbridge.com/?p=5273" rel="noopener noreferrer"&gt;How to Run a Java JAR from C#&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Edge Cases to Handle
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Classpath hell: Use -cp or the CLASSPATH environment variable. On Windows, separate entries with ;; on Linux/macOS, use :.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JVM not found: Check that java is on the system PATH or pass JAVA_HOME explicitly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Large output: For big payloads, write to a temp file instead of piping through stdout.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Process leaks: Always use using and kill on timeout — orphaned JVM processes eat server memory.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Every call spawns a new &lt;strong&gt;JVM&lt;/strong&gt;. That's &lt;strong&gt;50–200ms of startup overhead&lt;/strong&gt; &lt;em&gt;per invocation&lt;/em&gt;, plus the memory cost of a full JVM instance. If you're making more than a handful of calls, this approach doesn't scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  Method 2: IKVM — Compile Java Bytecode to .NET
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ikvmnet/ikvm" rel="noopener noreferrer"&gt;IKVM&lt;/a&gt; converts Java &lt;strong&gt;bytecode&lt;/strong&gt; into .NET assemblies. You run ikvmc against a JAR file and get a DLL you can reference directly in your C# project. Your Java code literally runs on the &lt;strong&gt;CLR&lt;/strong&gt; — no JVM required.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use It
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Java library is self-contained with few dependencies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You need tight, low-latency integration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're okay with some compatibility limitations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Example
&lt;/h3&gt;

&lt;p&gt;First, convert the JAR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install IKVM (community fork targets .NET 6+)&lt;/span&gt;

dotnet add package IKVM

&lt;span class="c"&gt;# Or use the command-line converter&lt;/span&gt;

ikvmc &lt;span class="nt"&gt;-target&lt;/span&gt;:library &lt;span class="nt"&gt;-out&lt;/span&gt;:MyJavaLib.dll mylib.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use it from C# like any other .NET library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;com.example.mylib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IkvmExample&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;RunJavaCodeInCSharp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// Java classes are now .NET classes&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mylib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;JsonParser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Call Java methods directly — compiled to IL bytecode&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{\"key\": \"value\"}"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Parsed: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Java collections work but need casting&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ArrayList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"item1"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"item2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;iterator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterator&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasNext&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iterator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;IKVM was a remarkable project, but it has real constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Incomplete JDK coverage: Not every javax. or java. class is implemented. Swing, AWT, and many java.nio features are missing or broken.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reflection edge cases: Java code relying heavily on reflection may behave differently.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Native dependencies: If your JAR depends on native JNI libraries, IKVM can't help.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maintenance status: The original project was abandoned. The &lt;a href="https://github.com/ikvmnet/ikvm" rel="noopener noreferrer"&gt;ikvm-revived&lt;/a&gt; community fork targets .NET 6+ but coverage varies.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 Migrating away from IKVM? See our guide on &lt;a href="https://jnbridge.com/?p=5275" rel="noopener noreferrer"&gt;Migrating from IKVM to JNBridgePro&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For simple, pure-Java libraries, IKVM is elegant. For anything touching the filesystem, networking, or native code, expect surprises.&lt;/p&gt;




&lt;h2&gt;
  
  
  Method 3: JNI via C++/CLI Wrapper
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Java Native Interface (JNI)&lt;/strong&gt; is the official way for native code to interact with the JVM. C++/CLI lets you write code that lives in both the .NET and native worlds, making it possible to load a JVM inside your .NET process and call Java methods through &lt;strong&gt;JNI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the most powerful — and most painful — approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use It
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You need maximum performance and control over marshaling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're comfortable with C++ and manual memory management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You have a dedicated team to maintain the interop layer&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Example
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;C++/CLI Bridge (JavaBridge.cpp):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Compile as C++/CLI: /clr&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;jni.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#using &amp;lt;mscorlib.dll&amp;gt;
&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Runtime&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;InteropServices&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JavaBridge&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="nl"&gt;private:&lt;/span&gt;

    &lt;span class="n"&gt;JavaVM&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;jvm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;JNIEnv&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="nl"&gt;public:&lt;/span&gt;

    &lt;span class="n"&gt;JavaBridge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;classPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;JavaVMInitArgs&lt;/span&gt; &lt;span class="n"&gt;vmArgs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;JavaVMOption&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;


&lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;cpPtr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;StringToHGlobalAnsi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

            &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-Djava.class.path={0}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classPath&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;optionString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;

            &lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToPointer&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;


&lt;span class="n"&gt;vmArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JNI_VERSION_1_8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;vmArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;vmArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="n"&gt;vmArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ignoreUnrecognized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JNI_FALSE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="n"&gt;jint&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JNI_CreateJavaVM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

            &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;jvm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;vmArgs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;FreeHGlobal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpPtr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;JNI_OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;gcnew&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

                &lt;span class="s"&gt;"Failed to create JVM: error {0}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;CallStaticMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

        &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;methodName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// Convert .NET strings to native for JNI&lt;/span&gt;

        &lt;span class="n"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;clsName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Marshal&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;StringToHGlobalAnsi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;jclass&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;FindClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

            &lt;span class="k"&gt;static_cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clsName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ToPointer&lt;/span&gt;&lt;span class="p"&gt;()));&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;nullptr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;gcnew&lt;/span&gt; &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

                &lt;span class="s"&gt;"Java class not found: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;className&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;span class="c1"&gt;// ... method lookup, call, string marshaling ...&lt;/span&gt;

        &lt;span class="c1"&gt;// (Full implementation requires ~50 lines of&lt;/span&gt;

        &lt;span class="c1"&gt;//  careful memory management)&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;JavaBridge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jvm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;jvm&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;DestroyJavaVM&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;C# Usage:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bridge&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JavaBridge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

    &lt;span class="s"&gt;@"C:\myapp\libs\mylib.jar"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CallStaticMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

    &lt;span class="s"&gt;"com/example/TextProcessor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="s"&gt;"processText"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="s"&gt;"Hello from C#!"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Most Teams Don't Do This
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You must maintain C++/CLI code — a language most .NET developers don't know&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Manual JNI string/array/object marshaling is tedious and error-prone&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;One null-pointer mistake crashes your entire process (segfault, not a managed exception)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Only one JVM per process (JNI limitation)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Every new Java method requires more C++ glue code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Windows-only if using C++/CLI (use P/Invoke on Linux)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the "build your own bridge" option. It works, but you're signing up to maintain it forever.&lt;/p&gt;




&lt;h2&gt;
  
  
  Method 4: gRPC Sidecar — Run Java as a Microservice
&lt;/h2&gt;

&lt;p&gt;Instead of running Java &lt;em&gt;inside&lt;/em&gt; your .NET process, run it &lt;em&gt;alongside&lt;/em&gt; as a separate service. Define your interface in &lt;a href="https://protobuf.dev/" rel="noopener noreferrer"&gt;Protocol Buffers&lt;/a&gt;, generate clients for both languages, and communicate over &lt;strong&gt;gRPC&lt;/strong&gt;. This is the modern, cloud-native approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use It
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You're already in a microservices architecture&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You want clean language boundaries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You need to scale the Java and .NET parts independently&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Latency of 2–5ms per call is acceptable&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Example
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Define the service (calculator.proto):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight protobuf"&gt;&lt;code&gt;&lt;span class="na"&gt;syntax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"proto3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;span class="kd"&gt;service&lt;/span&gt; &lt;span class="n"&gt;Calculator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;Calculate&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CalcRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CalcResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;rpc&lt;/span&gt; &lt;span class="n"&gt;BatchCalculate&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;CalcRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;returns&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="n"&gt;CalcResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;CalcRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;expression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;int32&lt;/span&gt; &lt;span class="na"&gt;precision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;CalcResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;formatted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. C# client:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Grpc.Net.Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JavaGrpcClient&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IDisposable&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;GrpcChannel&lt;/span&gt; &lt;span class="n"&gt;_channel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CalculatorClient&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;JavaGrpcClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:50051"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="n"&gt;_channel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GrpcChannel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ForAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;_client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculatorClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_channel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Formatted&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&lt;/span&gt;

        &lt;span class="nf"&gt;CalculateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;precision&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;CalcRequest&lt;/span&gt;

            &lt;span class="p"&gt;{&lt;/span&gt;

                &lt;span class="n"&gt;Expression&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

                &lt;span class="n"&gt;Precision&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;precision&lt;/span&gt;

            &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Formatted&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_channel&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JavaGrpcClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;formatted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

    &lt;span class="s"&gt;"(3.14159 * 2) + 1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Result: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;formatted&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "7.2832"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trade-offs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clean separation of concerns&lt;/td&gt;
&lt;td&gt;Network overhead (2–5ms/call)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language-independent contracts&lt;/td&gt;
&lt;td&gt;Must maintain .proto files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Independently scalable&lt;/td&gt;
&lt;td&gt;Two processes to deploy and monitor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Easy to test in isolation&lt;/td&gt;
&lt;td&gt;Serialization cost for complex objects&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Method 5: JNBridgePro — In-Process Java/.NET Bridge
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jnbridge.com/jnbridgepro/how-to-call-java-from-csharp" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt; loads the &lt;strong&gt;JVM&lt;/strong&gt; inside your .NET process and lets you call Java classes as if they were native C# objects. You use a &lt;strong&gt;proxy generation&lt;/strong&gt; tool to create .NET wrappers for your Java classes, then call them with normal C# syntax. No &lt;strong&gt;JNI&lt;/strong&gt; glue code, no process management, no serialization.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to Use It
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You need low-latency, high-frequency calls to Java code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You want to pass complex objects between Java and .NET without serialization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You need Java callbacks into .NET (bidirectional interop)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You don't want to maintain interop infrastructure yourself&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Code Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;com.jnbridge.jnbcore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;com.example.mylib&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Generated proxies&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JNBridgeExample&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;RunJavaInsideDotNet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// Initialize — starts a JVM in-process&lt;/span&gt;

        &lt;span class="n"&gt;DotNetSide&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JNBLicenseInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"license.dat"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;JNBClassPathInfo&lt;/span&gt;

        &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="n"&gt;ClassPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;

            &lt;span class="p"&gt;{&lt;/span&gt;

                &lt;span class="s"&gt;"/app/libs/mylib.jar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

                &lt;span class="s"&gt;"/app/libs/dependency.jar"&lt;/span&gt;

            &lt;span class="p"&gt;},&lt;/span&gt;

            &lt;span class="n"&gt;JvmPath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/usr/lib/jvm/java-17/lib/server/libjvm.so"&lt;/span&gt;

        &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;

        &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="c1"&gt;// Use Java objects like C# objects&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mylib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DataProcessor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// .NET types are marshaled automatically&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"batch"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"threads"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;valueOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

            &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Process data&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ArrayList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;++)&lt;/span&gt;
                &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"record-&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

                &lt;span class="s"&gt;$"Processed &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt; records"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;finally&lt;/span&gt;

        &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="n"&gt;DotNetSide&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shutdown&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What Makes It Different
&lt;/h3&gt;

&lt;p&gt;JNBridgePro handles the hard parts you'd have to build yourself with JNI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Type marshaling: Java strings, primitives, arrays, and collections convert automatically between the JVM and CLR&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Exception bridging: Java exceptions become .NET exceptions with full stack traces&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Garbage collection: Objects on both sides are properly tracked and collected&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Bidirectional calls: .NET code can call Java, and Java can call back into .NET&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Proxy generation: Point at a JAR, get .NET wrapper classes — no manual coding&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a commercial product, which is the main barrier. But if you're evaluating the &lt;strong&gt;best way to run Java from .NET&lt;/strong&gt; in production, the license cost is typically less than the engineering time to build and maintain a JNI wrapper or gRPC layer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔗 See how JNBridgePro compares to other &lt;a href="https://jnbridge.com/?p=5274" rel="noopener noreferrer"&gt;Java–C# bridge tools&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Performance Benchmarks
&lt;/h2&gt;

&lt;p&gt;These benchmarks measure calling a Java method that concatenates two strings — a minimal operation to isolate &lt;strong&gt;interop&lt;/strong&gt; overhead. Environment: .NET 8, Java 17, Windows 11, 16GB RAM.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;JVM Startup&lt;/th&gt;
&lt;th&gt;Per-Call Latency&lt;/th&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;th&gt;Throughput (calls/sec)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Process.Start&lt;/td&gt;
&lt;td&gt;~150ms/call&lt;/td&gt;
&lt;td&gt;~50–200ms&lt;/td&gt;
&lt;td&gt;~50MB/process&lt;/td&gt;
&lt;td&gt;~5–20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IKVM&lt;/td&gt;
&lt;td&gt;0 (no JVM)&lt;/td&gt;
&lt;td&gt;~0.1ms&lt;/td&gt;
&lt;td&gt;~20–50MB&lt;/td&gt;
&lt;td&gt;~500,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JNI/C++CLI&lt;/td&gt;
&lt;td&gt;~300ms (once)&lt;/td&gt;
&lt;td&gt;~0.05ms&lt;/td&gt;
&lt;td&gt;~30MB&lt;/td&gt;
&lt;td&gt;~1,000,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gRPC Sidecar&lt;/td&gt;
&lt;td&gt;~800ms (once)&lt;/td&gt;
&lt;td&gt;~2–5ms&lt;/td&gt;
&lt;td&gt;~100MB (separate)&lt;/td&gt;
&lt;td&gt;~5,000–20,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JNBridgePro&lt;/td&gt;
&lt;td&gt;~400ms (once)&lt;/td&gt;
&lt;td&gt;~0.1ms&lt;/td&gt;
&lt;td&gt;~40MB&lt;/td&gt;
&lt;td&gt;~500,000+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt; If you're making more than a few calls per second, &lt;strong&gt;Process.Start&lt;/strong&gt; is the wrong tool. The &lt;strong&gt;in-process&lt;/strong&gt; methods (IKVM, JNI, JNBridgePro) are orders of magnitude faster for repeated calls.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which Method Should You Use?
&lt;/h2&gt;

&lt;p&gt;Follow this decision tree:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many times do you call Java per request?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;Once or never (batch job, CLI tool):&lt;/strong&gt; Use &lt;strong&gt;Process.Start&lt;/strong&gt;. Simple, built-in, and the startup cost doesn't matter for single invocations.&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;A few times (&amp;lt; 100/sec):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Already running microservices? → &lt;strong&gt;gRPC Sidecar&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Monolith? → &lt;strong&gt;JNBridgePro&lt;/strong&gt; or &lt;strong&gt;gRPC&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;→ &lt;strong&gt;Hundreds or thousands of times:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Pure Java library, no native deps? → Try &lt;strong&gt;IKVM&lt;/strong&gt; first&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;IKVM doesn't cover your APIs? → &lt;strong&gt;JNBridgePro&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Zero budget + C++ expertise? → &lt;strong&gt;JNI/C++CLI&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Do you need bidirectional calls (Java calling back into .NET)?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;strong&gt;JNBridgePro&lt;/strong&gt; or JNI (painful)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cross-platform requirement?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;→ Process.Start, gRPC, IKVM, and JNBridgePro all work on Windows and Linux. JNI via C++/CLI is Windows-only (use P/Invoke on Linux).&lt;/p&gt;




&lt;h2&gt;
  
  
  How Do You Handle Java Dependencies from C#?
&lt;/h2&gt;

&lt;p&gt;Build a &lt;strong&gt;fat JAR&lt;/strong&gt; (using &lt;a href="https://maven.apache.org/plugins/maven-shade-plugin/" rel="noopener noreferrer"&gt;Maven Shade Plugin&lt;/a&gt; or Gradle Shadow) that bundles all dependencies into a single file. This gives you one JAR to reference in your &lt;strong&gt;classpath&lt;/strong&gt;, regardless of which interop method you choose.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;IKVM&lt;/strong&gt;, convert the fat JAR with ikvmc. For &lt;strong&gt;gRPC&lt;/strong&gt;, package it in a container with all dependencies. For &lt;strong&gt;JNBridgePro&lt;/strong&gt;, point the &lt;strong&gt;proxy generation&lt;/strong&gt; tool at the fat JAR and it resolves all classes automatically.&lt;/p&gt;

&lt;p&gt;Key pitfalls to avoid:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Classpath separator: Use ; on Windows, : on Linux/macOS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spaces in paths: Always quote JAR paths&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JAVA_HOME: Set it explicitly rather than relying on system PATH&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Correct cross-platform classpath construction&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;separator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RuntimeInformation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsOSPlatform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;

    &lt;span class="n"&gt;OSPlatform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Windows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;";"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;jars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"\"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\""&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Can You Run Java from C# Without a JDK?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;IKVM is the only method that doesn't require a JVM&lt;/strong&gt; — it compiles Java &lt;strong&gt;bytecode&lt;/strong&gt; to run directly on the &lt;strong&gt;CLR&lt;/strong&gt;. Every other method needs at least a JRE:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Process.Start needs a JRE on the same machine&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JNI and JNBridgePro need a JVM library (libjvm.so / jvm.dll)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;gRPC needs a JRE wherever the Java sidecar runs (which can be a Docker container)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If eliminating the JVM dependency is your primary goal and the Java library is pure Java, IKVM is your best option. For everything else, bundle a JRE with your deployment or use a container.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the best way to run Java from .NET in production?
&lt;/h3&gt;

&lt;p&gt;It depends on your call pattern. For &lt;strong&gt;high-frequency calls&lt;/strong&gt; in a monolithic app, an &lt;strong&gt;in-process&lt;/strong&gt; bridge like &lt;a href="https://jnbridge.com/jnbridgepro/how-to-call-java-from-csharp" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt; or IKVM gives the best &lt;strong&gt;latency&lt;/strong&gt;. For cloud-native architectures, a &lt;strong&gt;gRPC sidecar&lt;/strong&gt; provides cleaner operational boundaries. &lt;strong&gt;Process.Start&lt;/strong&gt; is only suitable for infrequent, batch-style operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I run Java code in C# on Linux?
&lt;/h3&gt;

&lt;p&gt;Yes. &lt;strong&gt;Process.Start&lt;/strong&gt; and &lt;strong&gt;gRPC&lt;/strong&gt; work on any OS. &lt;strong&gt;IKVM&lt;/strong&gt; works cross-platform since it runs on the &lt;strong&gt;CLR&lt;/strong&gt;. &lt;strong&gt;JNI&lt;/strong&gt; works on Linux but requires P/Invoke instead of C++/CLI. &lt;strong&gt;JNBridgePro&lt;/strong&gt; supports both Windows and Linux.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do error and exception handling work across Java and C#?
&lt;/h3&gt;

&lt;p&gt;Each method handles Java exceptions differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Process.Start: Check stderr and exit codes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;IKVM: Java exceptions become .NET exceptions (type names preserved)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JNI: You must manually check and clear exceptions — unhandled ones crash the process&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;gRPC: Map Java exceptions to &lt;a href="https://grpc.io/docs/guides/status-codes/" rel="noopener noreferrer"&gt;gRPC status codes&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;JNBridgePro: Java exceptions become .NET exceptions with original stack traces intact&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Is there a free way to run Java from C# with low latency?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;IKVM&lt;/strong&gt; (open source) gives low &lt;strong&gt;latency&lt;/strong&gt; for pure-Java libraries. &lt;strong&gt;JNI&lt;/strong&gt; is free but demands significant C++ expertise. &lt;strong&gt;gRPC&lt;/strong&gt; is free but adds network overhead. There's no free option that combines low latency, broad compatibility, and low maintenance — that's the gap commercial tools like &lt;strong&gt;JNBridgePro&lt;/strong&gt; fill.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;There's no single "best way to &lt;strong&gt;run Java from .NET&lt;/strong&gt;" — it depends on how tightly you need Java and C# to interact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Quick and dirty: Process.Start&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pure Java library, no native deps: Try IKVM&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Microservices architecture: gRPC sidecar&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Production integration, zero maintenance overhead: &lt;a href="https://jnbridge.com/jnbridgepro/how-to-call-java-from-csharp" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maximum control, have C++ skills: JNI&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whatever you choose, match the integration depth to your actual requirements. Don't build a gRPC service layer when Process.Start&lt;code&gt;will do, and don't shell out to&lt;/code&gt;java.exe` a thousand times per second when an &lt;strong&gt;in-process&lt;/strong&gt; bridge exists.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Ready to try in-process Java/.NET integration?&lt;/strong&gt; &lt;a href="https://jnbridge.com/download/download-jnbridgepro" rel="noopener noreferrer"&gt;Download the JNBridgePro free trial →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to see it in action?&lt;/strong&gt; &lt;a href="https://jnbridge.com/about/contact" rel="noopener noreferrer"&gt;Schedule a technical demo&lt;/a&gt; — we’ll walk through your specific Java libraries and show you working interop in real time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explore code samples and tutorials&lt;/strong&gt; in the &lt;a href="https://jnbridge.com/software/jnbridgepro/developer-center/demos" rel="noopener noreferrer"&gt;JNBridgePro Developer Center →&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://jnbridge.com/jnbridgepro/run-java-from-c-5-methods-with-code-examples" rel="noopener noreferrer"&gt;JNBridge blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>interop</category>
    </item>
    <item>
      <title>C# JVM Guide: JDK, CLR &amp; Java Interop for .NET</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Mon, 04 May 2026 13:21:25 +0000</pubDate>
      <link>https://dev.to/jnbridge/c-jvm-guide-jdk-clr-java-interop-for-net-2mo</link>
      <guid>https://dev.to/jnbridge/c-jvm-guide-jdk-clr-java-interop-for-net-2mo</guid>
      <description>&lt;p&gt;If you mostly live in .NET, the Java platform can look like a parallel universe: JVM, JDK, JARs, app servers, bytecode. The useful shortcut is to map each concept back to something you already know from C# and the CLR.&lt;/p&gt;

&lt;p&gt;This guide is a translation layer for .NET developers: what the JVM is, how the JDK compares to the .NET SDK, and what your real options are when a C# system needs to work with Java code in production.&lt;/p&gt;




&lt;p&gt;&amp;gt; &lt;strong&gt;TL;DR — Key Takeaways&lt;/strong&gt;&lt;br&gt;
&amp;gt;&lt;br&gt;
&amp;gt; – The &lt;strong&gt;C# JVM equivalent&lt;/strong&gt; is the &lt;strong&gt;Common Language Runtime (CLR)&lt;/strong&gt; — both are managed-code virtual machines that handle bytecode execution, garbage collection, and JIT compilation.&lt;br&gt;
&amp;gt; – The &lt;strong&gt;JDK&lt;/strong&gt; maps directly to the &lt;strong&gt;.NET SDK&lt;/strong&gt; — both bundle a compiler, runtime, and standard class library.&lt;br&gt;
&amp;gt; – You &lt;strong&gt;cannot run C# directly on the JVM&lt;/strong&gt; (or Java on the CLR) without translation tools.&lt;br&gt;
&amp;gt; – The best way to bridge C# and Java in production is &lt;strong&gt;in-process interop&lt;/strong&gt; — tools like &lt;a href="https://jnbridge.com/download/download-jnbridgepro" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt; let you call Java objects from C# (and vice versa) without REST wrappers or rewrites.&lt;br&gt;
&amp;gt; – IKVM is limited to Java 8 bytecode; for modern JDK versions, you need a commercially supported solution.&lt;/p&gt;




&lt;p&gt;If you’re a .NET developer, you’ve almost certainly encountered the &lt;strong&gt;C# JVM&lt;/strong&gt; question: how do these two platforms compare, can they interoperate, and what happens when your application needs both? This guide answers every part of that question from a .NET perspective.&lt;/p&gt;

&lt;p&gt;We’ll walk through the Java platform — &lt;strong&gt;JVM&lt;/strong&gt;, &lt;strong&gt;JDK&lt;/strong&gt;, and &lt;strong&gt;J2EE&lt;/strong&gt; — drawing clear parallels to the .NET ecosystem you already know. Then we’ll cover every realistic approach to bridging the C# JVM divide, from REST APIs to &lt;strong&gt;in-process interop with JNBridgePro&lt;/strong&gt;.&lt;/p&gt;




&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;


&lt;li&gt;What Is the JVM? (The .NET Developer’s Answer)&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;The JDK Explained for .NET Developers&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;CLR vs. JVM: Full Platform Comparison&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;What Is the Difference Between J2EE and ASP.NET?&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;Can C# Run on the JVM?&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;Can Java Run on the CLR? IKVM and GraalVM&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;How to Connect C# and Java: The 4 Real Options&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;When You Need In-Process C# JVM Bridging&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;Getting Started with JNBridgePro&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;FAQ: C# JVM, JDK, and .NET Java Interop&lt;/li&gt;
&lt;br&gt;


&lt;h2 id="what-is-the-jvm-the-net-developers-answer"&gt;What Is the JVM? (The .NET Developer’s Answer)&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Java Virtual Machine (JVM)&lt;/strong&gt; is Java’s equivalent of the &lt;strong&gt;Common Language Runtime (CLR)&lt;/strong&gt;. If you understand one, you already understand most of the other.&lt;/p&gt;

&lt;p&gt;The CLR takes your compiled C# — in the form of &lt;strong&gt;Intermediate Language (IL)&lt;/strong&gt; — and executes it. It handles &lt;strong&gt;memory management&lt;/strong&gt;, &lt;strong&gt;garbage collection&lt;/strong&gt;, &lt;strong&gt;JIT compilation&lt;/strong&gt;, type safety, and threading. The JVM does the same thing for &lt;strong&gt;Java bytecode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Both are &lt;strong&gt;stack-based virtual machines&lt;/strong&gt;. Both use just-in-time compilation to convert their respective intermediate representations into native machine code at runtime. Both provide automatic memory management through &lt;strong&gt;garbage collection&lt;/strong&gt;. The architectural similarities are not coincidental — the CLR was designed with full awareness of the JVM’s strengths and weaknesses.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;.NET&lt;/th&gt;
&lt;th&gt;Java&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source language&lt;/td&gt;
&lt;td&gt;C#, F#, VB.NET&lt;/td&gt;
&lt;td&gt;Java, Kotlin, Scala&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compiled to&lt;/td&gt;
&lt;td&gt;IL (Intermediate Language)&lt;/td&gt;
&lt;td&gt;Java bytecode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Executed by&lt;/td&gt;
&lt;td&gt;CLR (Common Language Runtime)&lt;/td&gt;
&lt;td&gt;JVM (Java Virtual Machine)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compilation unit&lt;/td&gt;
&lt;td&gt;Assembly (.dll / .exe)&lt;/td&gt;
&lt;td&gt;JAR / class files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The key difference is openness. The &lt;strong&gt;JVM specification&lt;/strong&gt; is an open standard with multiple competing implementations: Oracle’s HotSpot, Eclipse’s OpenJ9, Amazon’s Corretto, Azul’s Zulu, and &lt;a href="https://www.graalvm.org/" rel="noopener noreferrer"&gt;GraalVM&lt;/a&gt;. The CLR historically had one implementation from Microsoft, though &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/introduction" rel="noopener noreferrer"&gt;.NET is now open source&lt;/a&gt; and cross-platform.&lt;/p&gt;

&lt;h3 id="why-this-matters-to-you"&gt;Why This Matters to You&lt;/h3&gt;

&lt;p&gt;When someone says their library “runs on the JVM,” they mean it’s compiled to Java bytecode — just like when you say your library “targets .NET,” you mean it compiles to IL. The JVM is the &lt;strong&gt;execution engine&lt;/strong&gt;, not the language. This is why languages like Kotlin and Scala can coexist with Java: they all compile to the same bytecode and run on the same &lt;strong&gt;managed code&lt;/strong&gt; runtime.&lt;/p&gt;




&lt;h2 id="the-jdk-explained-for-net-developers"&gt;The JDK Explained for .NET Developers&lt;/h2&gt;

&lt;p&gt;If the JVM is the CLR, then the &lt;strong&gt;Java Development Kit (JDK)&lt;/strong&gt; is the &lt;strong&gt;.NET SDK&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The .NET SDK gives you everything needed to build .NET applications: the compiler (&lt;code&gt;csc&lt;/code&gt;/&lt;code&gt;dotnet build&lt;/code&gt;), the runtime (CLR), the &lt;strong&gt;base class libraries (BCL)&lt;/strong&gt;, and tooling. The JDK does the same for Java: it includes the Java compiler (&lt;code&gt;javac&lt;/code&gt;), the JVM, the &lt;strong&gt;standard library&lt;/strong&gt;, and development tools.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;.NET SDK&lt;/th&gt;
&lt;th&gt;JDK&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compiler&lt;/td&gt;
&lt;td&gt;Roslyn (&lt;code&gt;csc&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;javac&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;CLR&lt;/td&gt;
&lt;td&gt;JVM (HotSpot, etc.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard library&lt;/td&gt;
&lt;td&gt;BCL (Base Class Library)&lt;/td&gt;
&lt;td&gt;Java Standard Library&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package format&lt;/td&gt;
&lt;td&gt;NuGet (.nupkg)&lt;/td&gt;
&lt;td&gt;JAR / Maven artifact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build tool&lt;/td&gt;
&lt;td&gt;MSBuild / &lt;code&gt;dotnet&lt;/code&gt; CLI&lt;/td&gt;
&lt;td&gt;Maven / Gradle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Package manager&lt;/td&gt;
&lt;td&gt;NuGet&lt;/td&gt;
&lt;td&gt;Maven Central&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REPL&lt;/td&gt;
&lt;td&gt;C# Interactive&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;jshell&lt;/code&gt; (JDK 9+)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There’s also the &lt;strong&gt;JRE (Java Runtime Environment)&lt;/strong&gt; — a subset of the JDK containing only what’s needed to &lt;em&gt;run&lt;/em&gt; Java applications. The .NET world had a similar split historically: you’d install the “.NET Framework Runtime” on servers and the full SDK on dev machines.&lt;/p&gt;

&lt;h3 id="c-jdk-compatibility-what-version-do-you-need"&gt;C# JDK Compatibility: What Version Do You Need?&lt;/h3&gt;

&lt;p&gt;Java now follows a &lt;strong&gt;six-month release cadence&lt;/strong&gt; with &lt;strong&gt;Long-Term Support (LTS)&lt;/strong&gt; versions every two years. As of 2026, the current LTS versions are &lt;strong&gt;Java 17, 21, and 25&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you’re connecting C# to JVM-based systems, you’ll typically need &lt;strong&gt;JDK 11 or later&lt;/strong&gt; installed. JNBridgePro supports a range of JDK versions — see the &lt;a href="https://jnbridge.com/?p=5277" rel="noopener noreferrer"&gt;latest .NET 9 and Java 21 compatibility details&lt;/a&gt;.&lt;/p&gt;




&lt;h2 id="clr-vs-jvm-full-platform-comparison"&gt;CLR vs. JVM: Full Platform Comparison&lt;/h2&gt;

&lt;p&gt;Here’s a comprehensive side-by-side for .NET developers evaluating the &lt;strong&gt;.NET JVM&lt;/strong&gt; landscape:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;JVM / Java&lt;/th&gt;
&lt;th&gt;CLR / .NET&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Other languages&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Kotlin, Scala, Groovy, Clojure&lt;/td&gt;
&lt;td&gt;F#, VB.NET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JVM (HotSpot, OpenJ9, GraalVM)&lt;/td&gt;
&lt;td&gt;CLR (.NET Runtime)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Intermediate format&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Java bytecode (.class)&lt;/td&gt;
&lt;td&gt;IL / MSIL (.dll)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Garbage collection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;G1, ZGC, Shenandoah (pluggable)&lt;/td&gt;
&lt;td&gt;Generational GC (workstation/server)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JIT compilation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;C1/C2 tiered (HotSpot)&lt;/td&gt;
&lt;td&gt;RyuJIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AOT compilation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GraalVM Native Image&lt;/td&gt;
&lt;td&gt;.NET Native AOT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Generics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Type erasure (no reification)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Reified&lt;/strong&gt; (preserved at runtime)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Value types&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Project Valhalla (preview)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Structs&lt;/strong&gt; (first-class)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Properties&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Convention-based getters/setters&lt;/td&gt;
&lt;td&gt;Language-level &lt;code&gt;get&lt;/code&gt;/&lt;code&gt;set&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Async model&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Virtual Threads (Java 21+)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enterprise framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Jakarta EE (formerly J2EE)&lt;/td&gt;
&lt;td&gt;ASP.NET Core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ORM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hibernate, JPA&lt;/td&gt;
&lt;td&gt;Entity Framework Core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cross-platform&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (since inception)&lt;/td&gt;
&lt;td&gt;Yes (.NET 5+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;License&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OpenJDK (GPLv2+CE)&lt;/td&gt;
&lt;td&gt;MIT License&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few things stand out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Generics&lt;/strong&gt;: Java’s &lt;strong&gt;type erasure&lt;/strong&gt; means generic type information is lost at runtime. If you’ve relied on reflection over generic types in C#, you’ll find Java’s approach limiting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value types&lt;/strong&gt;: C#’s &lt;code&gt;struct&lt;/code&gt; has no direct Java equivalent yet — &lt;a href="https://openjdk.org/projects/valhalla/" rel="noopener noreferrer"&gt;Project Valhalla&lt;/a&gt; is changing this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async&lt;/strong&gt;: Instead of C#’s &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt; state machines, Java 21 introduced &lt;strong&gt;Virtual Threads&lt;/strong&gt; — lightweight threads managed by the JVM that make blocking code scale like async code.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2 id="what-is-the-difference-between-j2ee-and-aspnet"&gt;What Is the Difference Between J2EE and ASP.NET?&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;J2EE&lt;/strong&gt; (Java 2 Platform, Enterprise Edition) is Java’s enterprise application framework — the equivalent of &lt;strong&gt;ASP.NET Core&lt;/strong&gt; plus the broader .NET enterprise ecosystem. J2EE defines specifications for servlets, EJBs, JMS messaging, JPA persistence, and more. It was renamed to Java EE and eventually transferred to the Eclipse Foundation as &lt;strong&gt;Jakarta EE&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The biggest architectural difference: Java enterprise apps deploy to an &lt;strong&gt;application server&lt;/strong&gt; (Tomcat, WildFly, WebSphere) that provides runtime services. ASP.NET Core is &lt;strong&gt;self-hosted&lt;/strong&gt; — your application &lt;em&gt;is&lt;/em&gt; the server via Kestrel.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Java / Jakarta EE&lt;/th&gt;
&lt;th&gt;.NET Equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Servlets / JSP&lt;/td&gt;
&lt;td&gt;ASP.NET Core MVC / Razor Pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JAX-RS (REST APIs)&lt;/td&gt;
&lt;td&gt;ASP.NET Core Web API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JPA / Hibernate&lt;/td&gt;
&lt;td&gt;Entity Framework Core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JMS (Java Message Service)&lt;/td&gt;
&lt;td&gt;Azure Service Bus, RabbitMQ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EJB (Enterprise JavaBeans)&lt;/td&gt;
&lt;td&gt;DI + services (no direct equivalent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDI (Dependency Injection)&lt;/td&gt;
&lt;td&gt;Microsoft.Extensions.DI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSF (JavaServer Faces)&lt;/td&gt;
&lt;td&gt;Blazor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application Server (Tomcat)&lt;/td&gt;
&lt;td&gt;Kestrel (self-hosted)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For .NET developers who need to integrate with &lt;strong&gt;J2EE or Jakarta EE services&lt;/strong&gt;, understanding this architecture is critical. Many enterprise Java systems expose &lt;strong&gt;EJBs or JMS queues&lt;/strong&gt; that don’t have REST endpoints. This is exactly where a &lt;a href="https://jnbridge.com/?p=5274" rel="noopener noreferrer"&gt;C# to Java bridge tool&lt;/a&gt; becomes essential — it lets your .NET code call into Java objects directly, without requiring the Java side to expose web services.&lt;/p&gt;




&lt;h2 id="can-c-run-on-the-jvm"&gt;Can C# Run on the JVM?&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;No — not directly.&lt;/strong&gt; C# compiles to &lt;strong&gt;IL (Intermediate Language)&lt;/strong&gt;, which the CLR executes. The JVM executes &lt;strong&gt;Java bytecode&lt;/strong&gt; — a completely different instruction set. You can’t take a .NET assembly and load it into the JVM any more than you can run an x86 binary on an ARM processor without translation.&lt;/p&gt;

&lt;p&gt;There have been historical attempts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mainsoft Grasshopper&lt;/strong&gt; (mid-2000s) compiled .NET bytecode to Java bytecode, allowing ASP.NET apps to run on Java application servers. It’s been &lt;strong&gt;dead for over a decade&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-compilation&lt;/strong&gt; is theoretically possible, but no one maintains such a tool today. The languages have diverged enough — value types, LINQ, &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;, properties, events — that faithful translation would be enormously complex.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The practical answer&lt;/strong&gt; isn’t running C# &lt;em&gt;on&lt;/em&gt; the JVM. It’s running C# and Java &lt;strong&gt;side by side&lt;/strong&gt; and letting them communicate through a bridge. The right approach depends on your use case — see the bridging options below.&lt;/p&gt;




&lt;h2 id="can-java-run-on-the-clr-ikvm-and-graalvm"&gt;Can Java Run on the CLR? IKVM and GraalVM&lt;/h2&gt;

&lt;h3 id="ikvmnet"&gt;IKVM.NET&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/IKVM.NET" rel="noopener noreferrer"&gt;IKVM&lt;/a&gt; was an open-source project that converted &lt;strong&gt;Java bytecode to .NET IL&lt;/strong&gt;, letting you reference Java libraries as .NET assemblies. For simple libraries, it worked well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The catch:&lt;/strong&gt; IKVM supports only &lt;strong&gt;Java 8 bytecode&lt;/strong&gt;. The original project went dormant around 2017. A community fork revived it with .NET Core support, but it still can’t handle Java 11+ features — modules, records, sealed classes, virtual threads. For production systems using modern Java, IKVM is not viable. See our detailed guide on &lt;a href="https://jnbridge.com/?p=5275" rel="noopener noreferrer"&gt;migrating from IKVM to JNBridgePro&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="graalvm"&gt;GraalVM&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;GraalVM&lt;/strong&gt; is Oracle’s &lt;strong&gt;polyglot virtual machine&lt;/strong&gt;. Its &lt;strong&gt;Native Image&lt;/strong&gt; tool can compile Java libraries into native shared libraries with C-compatible entry points, which C# can call via P/Invoke.&lt;/p&gt;

&lt;p&gt;This works but requires significant effort: you must define the C API surface, handle &lt;strong&gt;marshaling&lt;/strong&gt; manually, and deal with GraalVM’s closed-world assumptions. GraalVM is powerful technology, but it’s not designed to be a &lt;strong&gt;C# JVM bridge&lt;/strong&gt;.&lt;/p&gt;




&lt;h2 id="how-to-connect-c-and-java-the-4-real-options"&gt;How to Connect C# and Java: The 4 Real Options&lt;/h2&gt;

&lt;p&gt;When your application needs both .NET and Java, here are your realistic choices:&lt;/p&gt;

&lt;h3 id="1-rest-apis-web-services"&gt;1. REST APIs / Web Services&lt;/h3&gt;

&lt;p&gt;Wrap Java functionality in a REST API (Spring Boot, Quarkus) and call it from &lt;code&gt;HttpClient&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Works when the Java side is already a service&lt;/li&gt;
&lt;li&gt;❌ &lt;strong&gt;Serialization overhead&lt;/strong&gt; and network latency&lt;/li&gt;
&lt;li&gt;❌ Two services to deploy, monitor, and scale&lt;/li&gt;
&lt;li&gt;❌ Loss of &lt;strong&gt;type safety&lt;/strong&gt; at the boundary&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id="2-message-queues-kafka-rabbitmq"&gt;2. Message Queues (Kafka, RabbitMQ)&lt;/h3&gt;

&lt;p&gt;Good for &lt;strong&gt;asynchronous, event-driven architectures&lt;/strong&gt;. Not suitable when you need synchronous method-level calls.&lt;/p&gt;

&lt;h3 id="3-grpc-process-level-interop"&gt;3. gRPC / Process-Level Interop&lt;/h3&gt;

&lt;p&gt;Run the JVM and CLR as separate processes communicating via &lt;strong&gt;gRPC&lt;/strong&gt; or named pipes. Better than REST for performance, but still two processes and serialization overhead.&lt;/p&gt;

&lt;h3 id="4-in-process-bridging-with-jnbridgepro"&gt;4. In-Process Bridging with JNBridgePro&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://jnbridge.com/jnbridgepro/how-to-call-java-from-csharp" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt;&lt;/strong&gt; runs the JVM and CLR &lt;strong&gt;in the same process&lt;/strong&gt; with direct, in-memory method calls. No serialization. No network hops. No REST wrappers.&lt;/p&gt;

&lt;p&gt;“&lt;code&gt;csharp&lt;br&gt;
// Call a Java HashMap from C# — via JNBridgePro proxy&lt;br&gt;
var map = new java.util.HashMap();&lt;br&gt;
map.put("key", "value");&lt;br&gt;
string result = (string)map.get("key");&lt;br&gt;
&lt;/code&gt;“&lt;/p&gt;

&lt;p&gt;This isn’t calling a web service. The &lt;strong&gt;JVM is loaded in-process&lt;/strong&gt;, and method calls cross the JVM-CLR boundary directly in memory — with full &lt;strong&gt;IntelliSense&lt;/strong&gt;, type checking, and debugging support.&lt;/p&gt;

&lt;p&gt;You can also &lt;a href="https://jnbridge.com/jnbridgepro/how-to-call-csharp-from-java-complete-guide" rel="noopener noreferrer"&gt;call C# from Java&lt;/a&gt; using the same bridge, making it a &lt;strong&gt;bidirectional interop&lt;/strong&gt; solution.&lt;/p&gt;




&lt;h2 id="when-you-need-in-process-c-jvm-bridging"&gt;When You Need In-Process C# JVM Bridging&lt;/h2&gt;

&lt;p&gt;Let’s get specific about when &lt;strong&gt;in-process bridging&lt;/strong&gt; beats a service boundary.&lt;/p&gt;

&lt;h3 id="java-libraries-without-rest-wrappers"&gt;Java Libraries Without REST Wrappers&lt;/h3&gt;

&lt;p&gt;Your organization has a proprietary Java library for &lt;strong&gt;risk calculations&lt;/strong&gt;, PDF generation, or scientific computation — 500,000 lines of battle-tested code with no REST API. With JNBridgePro, you &lt;strong&gt;reference the JAR directly&lt;/strong&gt; from your .NET project. No wrapper needed.&lt;/p&gt;

&lt;h3 id="enterprise-java-systems-net-j2ee-integration"&gt;Enterprise Java Systems (.NET J2EE Integration)&lt;/h3&gt;

&lt;p&gt;Your company runs &lt;strong&gt;enterprise Java&lt;/strong&gt; — EJBs, JMS queues, JNDI lookups. Your new frontend is ASP.NET Core. These Java enterprise APIs weren’t designed for HTTP. JNBridgePro lets your C# code interact with J2EE components natively — look up an EJB, subscribe to a JMS queue — all from C#.&lt;/p&gt;

&lt;h3 id="incremental-migration"&gt;Incremental Migration&lt;/h3&gt;

&lt;p&gt;You’re migrating from Java to .NET (or vice versa). Running everything as microservices &lt;strong&gt;doubles your infrastructure complexity&lt;/strong&gt;. In-process bridging lets you migrate one class at a time, with the bridge handling &lt;strong&gt;cross-platform calls&lt;/strong&gt; during the transition.&lt;/p&gt;

&lt;h3 id="performance-sensitive-integration"&gt;Performance-Sensitive Integration&lt;/h3&gt;

&lt;p&gt;When &lt;strong&gt;milliseconds matter&lt;/strong&gt; — financial systems, real-time analytics — HTTP serialization overhead is unacceptable. In-process calls via JNBridgePro happen in &lt;strong&gt;microseconds, not milliseconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&amp;gt; &lt;strong&gt;Why not just rewrite?&lt;/strong&gt; Because rewrites fail. Joel Spolsky called it “the single worst strategic mistake any software company can make.” The code you’re replacing encodes years of bug fixes and domain knowledge. &lt;strong&gt;Bridging lets you use what works&lt;/strong&gt; and build what’s new.&lt;/p&gt;




&lt;h2 id="getting-started-with-jnbridgepro"&gt;Getting Started with JNBridgePro&lt;/h2&gt;

&lt;p&gt;JNBridgePro supports two communication modes:&lt;/p&gt;


&lt;li&gt;
&lt;strong&gt;Shared memory (in-process)&lt;/strong&gt; — The JVM runs inside your .NET process. Fastest integration with &lt;strong&gt;zero serialization overhead&lt;/strong&gt;. Ideal for library-level calls.&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;
&lt;strong&gt;TCP/IP&lt;/strong&gt; — JVM and CLR run in separate processes or on separate machines. Use when in-process hosting isn’t feasible.&lt;/li&gt;

&lt;p&gt;The setup workflow:&lt;/p&gt;


&lt;li&gt;
&lt;strong&gt;Install JNBridgePro&lt;/strong&gt; and point it at your Java classes (JARs or class directories)&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;
&lt;strong&gt;Generate .NET proxies&lt;/strong&gt; using the GUI or command-line tool — creates C# classes mirroring the Java API&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;
&lt;strong&gt;Reference the proxy assembly&lt;/strong&gt; from your .NET project&lt;/li&gt;
&lt;br&gt;
&lt;li&gt;
&lt;strong&gt;Write C# code&lt;/strong&gt; that calls Java objects through the proxies — with full IntelliSense and debugging&lt;/li&gt;

&lt;p&gt;No Java code changes required. No REST endpoints to build. No serialization formats to define.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jnbridge.com/download/download-jnbridgepro" rel="noopener noreferrer"&gt;&lt;strong&gt;Download a free trial of JNBridgePro →&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jnbridge.com/software/jnbridgepro/developer-center/demos" rel="noopener noreferrer"&gt;&lt;strong&gt;Explore demos in the Developer Center →&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jnbridge.com/about/contact" rel="noopener noreferrer"&gt;&lt;strong&gt;Contact our team about your integration scenario →&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2 id="faq-c-jvm-jdk-and-net-java-interop"&gt;FAQ: C# JVM, JDK, and .NET Java Interop&lt;/h2&gt;

&lt;h3 id="does-c-run-in-a-vm-similar-to-javas-jvm"&gt;Does C# run in a VM similar to Java’s JVM?&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Yes.&lt;/strong&gt; C# runs on the &lt;strong&gt;Common Language Runtime (CLR)&lt;/strong&gt;, which serves the same role as the JVM. Both are managed-code virtual machines that execute an intermediate bytecode format, provide automatic garbage collection, enforce type safety, and use JIT compilation to generate native machine code at runtime. The CLR executes IL (Intermediate Language); the JVM executes Java bytecode. They are architecturally parallel but &lt;strong&gt;not interchangeable&lt;/strong&gt; — code compiled for one cannot run on the other without a bridge like &lt;a href="https://jnbridge.com/jnbridgepro/how-to-call-java-from-csharp" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="what-is-the-equivalent-of-the-jvm-in-c"&gt;What is the equivalent of the JVM in C#?&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;CLR (Common Language Runtime)&lt;/strong&gt; is the C# JVM equivalent. Both are stack-based virtual machines that compile an intermediate representation to native code at runtime. The key difference: the JVM was built for Java specifically, while the CLR was designed to be &lt;strong&gt;language-neutral&lt;/strong&gt;, supporting C#, F#, and VB.NET from the start.&lt;/p&gt;

&lt;h3 id="can-you-compile-c-to-run-on-the-jvm"&gt;Can you compile C# to run on the JVM?&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;No — there is no maintained tool that compiles C# to JVM bytecode.&lt;/strong&gt; Historical projects like Mainsoft Grasshopper attempted this but are long abandoned. The practical solution is &lt;strong&gt;interop, not porting&lt;/strong&gt;: run both runtimes and bridge them using JNBridgePro, REST APIs, or gRPC.&lt;/p&gt;

&lt;h3 id="what-happened-to-ikvm-can-it-replace-a-c-jvm-bridge"&gt;What happened to IKVM? Can it replace a C# JVM bridge?&lt;/h3&gt;

&lt;p&gt;IKVM converted Java bytecode to .NET IL, allowing Java libraries to run on the CLR. The original project went dormant around 2017; a community fork revived it with .NET Core support but &lt;strong&gt;remains limited to Java 8&lt;/strong&gt;. For modern Java (11+), enterprise integration, or production systems, &lt;a href="https://jnbridge.com/?p=5275" rel="noopener noreferrer"&gt;JNBridgePro is the recommended alternative&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="how-does-jnbridgepro-handle-c-jdk-compatibility"&gt;How does JNBridgePro handle C# JDK compatibility?&lt;/h3&gt;

&lt;p&gt;JNBridgePro is regularly updated to support &lt;strong&gt;new JDK releases&lt;/strong&gt; — including &lt;a href="https://jnbridge.com/?p=5277" rel="noopener noreferrer"&gt;Java 21 with .NET 9&lt;/a&gt;. It works with the JDK you already have installed. The proxy generation tool reads your Java classes regardless of JDK version (within supported ranges), and the runtime bridge handles all &lt;strong&gt;type conversion and marshaling&lt;/strong&gt; between the CLR and JVM.&lt;/p&gt;

&lt;h3 id="does-the-net-sdk-include-jvm-or-jdk-support"&gt;Does the .NET SDK include JVM or JDK support?&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;No.&lt;/strong&gt; The &lt;a href="https://learn.microsoft.com/en-us/dotnet/core/sdk" rel="noopener noreferrer"&gt;.NET SDK&lt;/a&gt; and the &lt;a href="https://openjdk.org/" rel="noopener noreferrer"&gt;JDK&lt;/a&gt; are completely separate toolkits. To work with both platforms, install them independently and use a bridging tool for interop.&lt;/p&gt;



&lt;h2&gt;
  
  
  The practical takeaway
&lt;/h2&gt;

&lt;p&gt;For many teams, the right answer is still a service boundary: HTTP, gRPC, queues, or another explicit integration layer. But when you need object-level calls, shared types, low-latency execution, or access to an existing Java library without rewriting it, you need something closer than another API wrapper.&lt;/p&gt;

&lt;p&gt;That is where Java/.NET bridging earns its keep: keep both runtimes, keep the working code, and connect them intentionally.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>java</category>
      <category>csharp</category>
      <category>interop</category>
    </item>
  </channel>
</rss>
