<?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>Forem: JNBridge</title>
    <description>The latest articles on Forem by JNBridge (@jnbridge).</description>
    <link>https://forem.com/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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3784282%2Fae8a97d2-48bf-4d6d-908f-a3c634c52efe.png</url>
      <title>Forem: JNBridge</title>
      <link>https://forem.com/jnbridge</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jnbridge"/>
    <language>en</language>
    <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://forem.com/jnbridge/run-java-from-c-5-methods-with-code-examples-2g6h</link>
      <guid>https://forem.com/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://forem.com/jnbridge/c-jvm-guide-jdk-clr-java-interop-for-net-2mo</link>
      <guid>https://forem.com/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>
    <item>
      <title>How to Use Java JARs and .NET DLLs Across Platforms</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Thu, 30 Apr 2026 13:23:37 +0000</pubDate>
      <link>https://forem.com/jnbridge/how-to-use-java-jars-and-net-dlls-across-platforms-5gf9</link>
      <guid>https://forem.com/jnbridge/how-to-use-java-jars-and-net-dlls-across-platforms-5gf9</guid>
      <description>&lt;p&gt;If you have ever tried to mix a Java JAR into a .NET app — or call a .NET DLL from Java — you quickly hit the same wall: the files look like libraries, but the runtimes do not understand each other.&lt;/p&gt;

&lt;p&gt;For developers, the useful question is architectural: do you wrap a process, expose REST/gRPC, drop into JNI/PInvoke, translate bytecode, or use an in-process bridge? Here are the tradeoffs a working team actually needs to choose between them.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;TL;DR / Key Takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;– You &lt;strong&gt;cannot directly reference a JAR in C#&lt;/strong&gt; or import a .NET DLL in Java — the runtimes are incompatible.&lt;/p&gt;

&lt;p&gt;– &lt;strong&gt;Process wrapping&lt;/strong&gt; is the fastest to set up but slowest at runtime (~50–200 ms per call).&lt;/p&gt;

&lt;p&gt;– &lt;strong&gt;REST/gRPC wrappers&lt;/strong&gt; work well for loosely coupled, low-frequency calls.&lt;/p&gt;

&lt;p&gt;– &lt;strong&gt;IKVM&lt;/strong&gt; converts Java bytecode to .NET IL but is &lt;strong&gt;stuck on Java 8&lt;/strong&gt; with no commercial support.&lt;/p&gt;

&lt;p&gt;– &lt;strong&gt;&lt;a href="https://jnbridge.com/download/download-jnbridgepro" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt;&lt;/strong&gt; provides &lt;strong&gt;in-process bridging at ~8 µs per call&lt;/strong&gt; with full object access and enterprise support.&lt;/p&gt;

&lt;p&gt;Using a &lt;strong&gt;JAR in C#&lt;/strong&gt; is one of the most common cross-platform interop challenges developers face. Whether you need to call a Java library from a .NET application or load a .NET DLL in a Java project, the two runtimes — JVM and CLR — don’t speak the same language. This guide covers every production-ready method for &lt;strong&gt;Java/.NET interoperability&lt;/strong&gt;, with real code, performance benchmarks, and honest trade-offs.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Why JARs and DLLs Are Incompatible&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Method 1: Process Wrapping (Quick and Dirty)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Method 2: REST/gRPC Service Wrapper&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Method 3: JNI — Low-Level Native Bridge&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Method 4: IKVM (Java on .NET — Legacy)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Method 5: JNBridgePro (In-Process Runtime Bridge)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How Do JAR-to-DLL Interop Methods Compare?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Which Method Should You Choose?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Common Pitfalls: Classpath, Type Mapping, and Memory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FAQ&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Get Started&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why JARs and DLLs Are Incompatible
&lt;/h2&gt;

&lt;p&gt;Java JARs contain &lt;strong&gt;bytecode&lt;/strong&gt; compiled for the JVM. .NET DLLs contain &lt;strong&gt;MSIL compiled for the CLR&lt;/strong&gt;. These are fundamentally different execution environments with separate memory management, type systems, and native interop models.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Java JAR&lt;/th&gt;
&lt;th&gt;.NET DLL (Assembly)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JVM (Java Virtual Machine)&lt;/td&gt;
&lt;td&gt;CLR (Common Language Runtime)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bytecode format&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Java bytecode (.class)&lt;/td&gt;
&lt;td&gt;CIL (.dll assemblies)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Memory management&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JVM garbage collector&lt;/td&gt;
&lt;td&gt;.NET garbage collector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type system&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Java type system&lt;/td&gt;
&lt;td&gt;Common Type System (CTS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Native interop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JNI&lt;/td&gt;
&lt;td&gt;P/Invoke, COM Interop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can’t “Add Reference” to a JAR in Visual Studio or &lt;code&gt;import&lt;/code&gt; a .NET assembly in Java. You need a &lt;strong&gt;runtime bridge&lt;/strong&gt;, a &lt;strong&gt;service wrapper&lt;/strong&gt;, or a &lt;strong&gt;bytecode translator&lt;/strong&gt;. Here are your five options — from simplest to most powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 1: Process Wrapping (Quick and Dirty)
&lt;/h2&gt;

&lt;p&gt;The simplest approach to using a &lt;strong&gt;JAR in C#&lt;/strong&gt;: launch a separate &lt;code&gt;java&lt;/code&gt; process and capture its output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running a Java JAR from C
&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;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JavaRunner&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="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;RunJar&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;args&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;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="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="s"&gt;"java"&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;args&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="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="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;output&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;StandardOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadToEnd&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;WaitForExit&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 process failed: &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;StandardError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadToEnd&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="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;For a deeper walkthrough, 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;h3&gt;
  
  
  Running a .NET DLL from Java
&lt;/h3&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;java.io.*&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;DotNetRunner&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="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;runDotNet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;dllPath&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&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;ProcessBuilder&lt;/span&gt; &lt;span class="n"&gt;pb&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;ProcessBuilder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dotnet"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dllPath&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="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;redirectErrorStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Process&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pb&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="nc"&gt;BufferedReader&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="nc"&gt;BufferedReader&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;InputStreamReader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInputStream&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;

&lt;span class="nc"&gt;StringBuilder&lt;/span&gt; &lt;span class="n"&gt;output&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;StringBuilder&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;line&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;readLine&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;waitFor&lt;/span&gt;&lt;span class="o"&gt;()&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dotnet process failed"&lt;/span&gt;&lt;span class="o"&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="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&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;&lt;strong&gt;Pros:&lt;/strong&gt; Simple, no dependencies, works everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; &lt;strong&gt;Slow&lt;/strong&gt; (~50–200 ms startup per call), limited to string I/O, no direct object access, no &lt;strong&gt;marshaling&lt;/strong&gt; of complex types, crude error handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; One-off batch jobs, scripts, quick prototypes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 2: REST/gRPC Service Wrapper
&lt;/h2&gt;

&lt;p&gt;Wrap your Java or .NET code as a microservice and call it over HTTP or gRPC. This avoids the per-call process startup overhead but adds &lt;strong&gt;network serialization&lt;/strong&gt; latency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exposing a Java JAR as a REST API
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;
&lt;span class="nd"&gt;@RestController&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;AnalyticsController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;AnalyticsEngine&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// from your JAR&lt;/span&gt;

&lt;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/analyze"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;AnalysisResult&lt;/span&gt; &lt;span class="nf"&gt;analyze&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;DataSet&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;process&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="o"&gt;}&lt;/span&gt;

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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Calling It from C\
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&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;"http://localhost:8080"&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;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;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;"/analyze"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dataset&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;analysis&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;result&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="nf"&gt;ReadFromJsonAsync&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;Pros:&lt;/strong&gt; Clean separation, language-agnostic, &lt;strong&gt;independently scalable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Network latency (~0.3–5 ms even on localhost), serialization overhead, operational complexity of running a separate service.&lt;/p&gt;

&lt;p&gt;For a detailed comparison of bridging vs. REST vs. gRPC, see &lt;a href="https://jnbridge.com/?p=5270" rel="noopener noreferrer"&gt;Bridge vs REST vs gRPC for Java/.NET Integration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Loosely coupled systems, infrequent calls, existing microservice architectures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 3: JNI — Low-Level Native Bridge
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jni/" rel="noopener noreferrer"&gt;Java Native Interface (JNI)&lt;/a&gt; lets Java call native code. You can chain it: &lt;strong&gt;Java ↔ JNI ↔ C/C++ ↔ P/Invoke ↔ .NET&lt;/strong&gt;. Technically possible, practically painful.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Java ←→ JNI ←→ Native C/C++ ←→ P/Invoke ←→ .NET CLR

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// Native bridge (C) — simplified&lt;/span&gt;

&lt;span class="n"&gt;JNIEXPORT&lt;/span&gt; &lt;span class="n"&gt;jstring&lt;/span&gt; &lt;span class="n"&gt;JNICALL&lt;/span&gt; &lt;span class="nf"&gt;Java_Bridge_callDotNet&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="n"&gt;jobject&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jstring&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="c1"&gt;// Load CLR, locate assembly, invoke method, marshal result&lt;/span&gt;

&lt;span class="c1"&gt;// ... hundreds of lines of platform-specific boilerplate ...&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;Pros:&lt;/strong&gt; Near-native performance, no network overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; &lt;strong&gt;Extremely complex&lt;/strong&gt; — manual memory management, &lt;strong&gt;type marshaling&lt;/strong&gt; nightmares, platform-specific builds, debugging across three runtimes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Almost nobody. Only justified when you need absolute maximum performance &lt;em&gt;and&lt;/em&gt; have deep C/C++ expertise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 4: IKVM (Java on .NET — Legacy)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/ikvmnet/ikvm" rel="noopener noreferrer"&gt;IKVM&lt;/a&gt; was an open-source project that translated Java &lt;strong&gt;bytecode&lt;/strong&gt; to .NET IL, letting you reference JARs directly as .NET &lt;strong&gt;assemblies&lt;/strong&gt;.&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;# Convert JAR to DLL (IKVM)&lt;/span&gt;

ikvmc &lt;span class="nt"&gt;-target&lt;/span&gt;:library analytics.jar &lt;span class="nt"&gt;-out&lt;/span&gt;:Analytics.dll

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// Then use Java classes like C# classes&lt;/span&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;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;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Current status:&lt;/strong&gt; IKVM is &lt;strong&gt;stuck on Java SE 8&lt;/strong&gt; (2014 APIs). Libraries using Java 11+ features — records, virtual threads (Java 21), modern &lt;code&gt;java.time&lt;/code&gt; — won't work. A community fork (&lt;a href="https://github.com/ikvmnet/ikvm" rel="noopener noreferrer"&gt;ikvm-revived&lt;/a&gt;) has made progress but lacks production readiness and commercial support.&lt;/p&gt;

&lt;p&gt;If you're currently on IKVM and hitting limitations, see &lt;a href="https://jnbridge.com/?p=5275" rel="noopener noreferrer"&gt;Migrating from IKVM to JNBridgePro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Legacy situations with Java 8 libraries only — and tolerance for risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Method 5: JNBridgePro (In-Process Runtime Bridge)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://jnbridge.com/download/download-jnbridgepro" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt;&lt;/strong&gt; runs both the JVM and CLR &lt;strong&gt;in the same process&lt;/strong&gt;, creating &lt;strong&gt;proxy classes&lt;/strong&gt; that let you use Java objects from C# (and vice versa) as if they were native. No serialization. No network. Direct &lt;strong&gt;reflection-based type mapping&lt;/strong&gt; with full access to properties, methods, fields, exceptions, and callbacks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using a Java JAR in C\
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// After generating .NET proxies for your JAR:&lt;/span&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;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="nf"&gt;AnalysisConfig&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;setThreshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.95&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;setMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"comprehensive"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Direct method calls — no serialization, no network&lt;/span&gt;

&lt;span class="n"&gt;AnalysisResult&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;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataset&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="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;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;$"Items: &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;getResults&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;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using a .NET DLL in Java
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;
&lt;span class="c1"&gt;// After generating Java proxies for your .NET assembly:&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;system.io.FileStream&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;system.io.StreamReader&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;FileStream&lt;/span&gt; &lt;span class="n"&gt;fs&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;FileStream&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data.bin"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;FileMode&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Open&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;StreamReader&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="nc"&gt;StreamReader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;content&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;ReadToEnd&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;Close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For step-by-step tutorials, see &lt;a href="https://jnbridge.com/jnbridgepro/how-to-call-java-from-csharp" rel="noopener noreferrer"&gt;How to Call Java from C#&lt;/a&gt; and &lt;a href="https://jnbridge.com/jnbridgepro/how-to-call-csharp-from-java-complete-guide" rel="noopener noreferrer"&gt;How to Call C# from Java&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; &lt;strong&gt;~8 µs per call&lt;/strong&gt;, direct object access, full &lt;strong&gt;type mapping&lt;/strong&gt;, supports modern Java (11, 17, 21) and .NET (6, 7, 8), commercial support with SLAs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Commercial license required, &lt;strong&gt;proxy&lt;/strong&gt; generation step, JVM + CLR in same process uses more memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Enterprise integration — high-frequency calls, complex object graphs, regulated industries (finance, healthcare).&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do JAR-to-DLL Interop Methods Compare?
&lt;/h2&gt;

&lt;p&gt;| | Method | Latency / Call | Setup | Object Access | Modern Java/C# | Production Ready |&lt;br&gt;
|---|---:|---|---|---|---|&lt;br&gt;
| &lt;strong&gt;Process wrapping&lt;/strong&gt; | ~50–200 ms | ⭐ Easy | ❌ String only | ✅ | ⚠️ Fragile |&lt;br&gt;
| &lt;strong&gt;REST/gRPC&lt;/strong&gt; | ~0.3–5 ms | ⭐⭐ Moderate | ❌ Serialized | ✅ | ✅ |&lt;br&gt;
| &lt;strong&gt;JNI (manual)&lt;/strong&gt; | ~0.1 ms | ⭐⭐⭐⭐⭐ Expert | ⚠️ Limited | ✅ | ⚠️ Risky |&lt;br&gt;
| &lt;strong&gt;IKVM&lt;/strong&gt; | ~0.01 ms | ⭐⭐ Moderate | ✅ Full | ❌ Java 8 only | ❌ Unmaintained |&lt;br&gt;
| &lt;strong&gt;JNBridgePro&lt;/strong&gt; | &lt;strong&gt;~0.008 ms&lt;/strong&gt; | ⭐⭐ Moderate | ✅ Full | ✅ | ✅ Enterprise |&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For 10,000 calls:&lt;/strong&gt; Process wrapping takes 8–33 minutes. REST takes 3–50 seconds. JNBridgePro takes &lt;strong&gt;0.08 seconds&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Which Method Should You Choose?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use Process Wrapping if&lt;/strong&gt; you need a quick one-off integration, calls are infrequent (batch processing), and you don't need direct &lt;strong&gt;Java object access&lt;/strong&gt; from .NET.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use REST/gRPC if&lt;/strong&gt; your systems are loosely coupled, you're already in a &lt;strong&gt;microservices architecture&lt;/strong&gt;, or calls happen fewer than 100 times per second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use JNBridgePro if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You need to call Java/.NET code &lt;strong&gt;thousands of times per second&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You need direct access to &lt;strong&gt;object properties, methods, and callbacks&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're integrating complex libraries — not just simple functions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You need &lt;strong&gt;commercial support&lt;/strong&gt; and SLAs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're in a &lt;strong&gt;regulated industry&lt;/strong&gt; that requires vendor backing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Avoid JNI&lt;/strong&gt; unless you have deep C/C++ expertise and a very specific performance requirement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Avoid IKVM&lt;/strong&gt; unless you work exclusively with Java 8-era libraries and accept the risk of an unmaintained project.&lt;/p&gt;
&lt;h2&gt;
  
  
  Common Pitfalls: Classpath, Type Mapping, and Memory
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Classpath Issues When Loading JARs
&lt;/h3&gt;

&lt;p&gt;When loading JARs from .NET — via Process wrapping or JNBridgePro — ensure all &lt;strong&gt;dependency JARs&lt;/strong&gt; are on the &lt;strong&gt;classpath&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="c1"&gt;// ❌ Wrong — missing dependencies&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;Arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"-jar analytics.jar"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Right — include all JARs on the classpath&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;classpath&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"analytics.jar;lib/commons-math3.jar;lib/slf4j-api.jar"&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;StartInfo&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;$"-cp &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="s"&gt; com.example.Main &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="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Type Mapping Gotchas Between Java and C\
&lt;/h3&gt;

&lt;p&gt;Java and .NET have subtly different &lt;strong&gt;type systems&lt;/strong&gt;. Watch these carefully during &lt;strong&gt;interop&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Java Type&lt;/th&gt;
&lt;th&gt;C# Equivalent&lt;/th&gt;
&lt;th&gt;Gotcha&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;byte&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sbyte&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Java byte is &lt;strong&gt;signed&lt;/strong&gt; (−128 to 127)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;char&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;char&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Both UTF-16, but Java char is unsigned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BigDecimal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;decimal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Different &lt;strong&gt;precision&lt;/strong&gt; semantics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LocalDate&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DateOnly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Different APIs, same concept&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;long&lt;/code&gt; / &lt;code&gt;Long&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;long&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Same size, but Java's boxed &lt;code&gt;Long&lt;/code&gt; ≠ C# &lt;code&gt;long&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Memory Management Across Runtimes
&lt;/h3&gt;

&lt;p&gt;When bridging Java and .NET, each runtime has its own &lt;strong&gt;garbage collector&lt;/strong&gt;. Objects on one side won't be collected until &lt;strong&gt;proxy references&lt;/strong&gt; on the other side are released. In long-running applications, failing to dispose cross-runtime references can cause &lt;strong&gt;memory leaks&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I Directly Reference a JAR File in Visual Studio?
&lt;/h3&gt;

&lt;p&gt;No. Visual Studio doesn't natively understand Java JAR files. &lt;strong&gt;JAR files contain Java bytecode&lt;/strong&gt;, which the .NET CLR cannot execute. You need either a bytecode translator like IKVM (limited to Java 8) or a &lt;strong&gt;runtime bridge&lt;/strong&gt; like JNBridgePro that generates .NET &lt;strong&gt;proxy&lt;/strong&gt; classes wrapping the JAR's contents. The proxies let you call Java methods from C# with full IntelliSense and type safety.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Java Load and Call Methods in a .NET DLL?
&lt;/h3&gt;

&lt;p&gt;Not directly. Java's classloader only understands Java &lt;strong&gt;bytecode&lt;/strong&gt; in &lt;code&gt;.class&lt;/code&gt; format. To &lt;strong&gt;load a .NET DLL in Java&lt;/strong&gt;, you need one of three approaches: a service wrapper (REST/gRPC), a native bridge through JNI, or &lt;a href="https://jnbridge.com/jnbridgepro/how-to-call-csharp-from-java-complete-guide" rel="noopener noreferrer"&gt;JNBridgePro which generates Java proxy classes&lt;/a&gt; for .NET &lt;strong&gt;assemblies&lt;/strong&gt; — giving you direct access to .NET objects from Java code.&lt;/p&gt;

&lt;h3&gt;
  
  
  What’s the Performance Difference Between REST and In-Process Bridging?
&lt;/h3&gt;

&lt;p&gt;REST calls add network overhead even on localhost — typically &lt;strong&gt;0.3–5 ms per call&lt;/strong&gt; including serialization and &lt;strong&gt;marshaling&lt;/strong&gt;. In-process bridging with JNBridgePro operates at &lt;strong&gt;~8 microseconds per call&lt;/strong&gt; with zero serialization. For 10,000 calls, that’s 3–50 seconds via REST versus 0.08 seconds via bridge — a &lt;strong&gt;60× to 600× improvement&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is IKVM Still Maintained?
&lt;/h3&gt;

&lt;p&gt;The original IKVM project ended in 2017. A community fork (&lt;a href="https://github.com/ikvmnet/ikvm" rel="noopener noreferrer"&gt;ikvm-revived&lt;/a&gt;) exists and has made progress, but it’s &lt;strong&gt;not production-ready for Java 11+&lt;/strong&gt; and has no commercial support. For production workloads requiring modern Java, &lt;a href="https://jnbridge.com/?p=5275" rel="noopener noreferrer"&gt;consider migrating from IKVM&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I Use a Java JAR in .NET Without Installing the JDK?
&lt;/h3&gt;

&lt;p&gt;With IKVM (Java 8 only), yes — it converts Java &lt;strong&gt;bytecode&lt;/strong&gt; to .NET IL, so no JVM is needed. With all other approaches (Process, JNBridgePro, JNI), you need a JRE/JDK. JNBridgePro can use an &lt;strong&gt;embedded JRE&lt;/strong&gt;, simplifying deployment.&lt;/p&gt;

&lt;h3&gt;
  
  
  What About Javonet or jni4net?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.javonet.com/" rel="noopener noreferrer"&gt;Javonet&lt;/a&gt; offers a reflection-style API for calling .NET from Java (and vice versa). &lt;a href="https://github.com/jni4net/jni4net" rel="noopener noreferrer"&gt;jni4net&lt;/a&gt; is an open-source JNI-based bridge but hasn’t been updated since 2015. JNBridgePro differs by providing &lt;strong&gt;compile-time proxy generation&lt;/strong&gt; with full &lt;strong&gt;type mapping&lt;/strong&gt;, IntelliSense support, and enterprise-grade reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Ready to integrate Java JARs in your .NET project — or .NET DLLs in Java?&lt;/p&gt;

&lt;p&gt;👉 &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;/strong&gt; and see how in-process bridging compares to your current approach. Most teams have a working integration within a day.&lt;/p&gt;

&lt;p&gt;📚 Explore the &lt;strong&gt;&lt;a href="https://jnbridge.com/software/jnbridgepro/developer-center/demos" rel="noopener noreferrer"&gt;JNBridgePro Developer Center&lt;/a&gt;&lt;/strong&gt; for demos, tutorials, and sample projects.&lt;/p&gt;

&lt;p&gt;💬 Have questions? &lt;strong&gt;&lt;a href="https://jnbridge.com/about/contact" rel="noopener noreferrer"&gt;Contact the JNBridge team&lt;/a&gt;&lt;/strong&gt; for architecture guidance or licensing details.&lt;/p&gt;




&lt;p&gt;If your use case needs direct calls across the Java/.NET boundary rather than a service wrapper, the original JNBridge guide includes the full production-oriented comparison and examples: &lt;a href="https://jnbridge.com/jnbridgepro/how-to-use-java-jars-and-net-dlls-across-platforms" rel="noopener noreferrer"&gt;How to Use Java JARs and .NET DLLs Across Platforms&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>interop</category>
    </item>
    <item>
      <title>Anatomy of a Shared Memory Bridge: How .NET Calls Java In-Process</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Mon, 27 Apr 2026 13:21:04 +0000</pubDate>
      <link>https://forem.com/jnbridge/anatomy-of-a-shared-memory-bridge-how-net-calls-java-in-process-205n</link>
      <guid>https://forem.com/jnbridge/anatomy-of-a-shared-memory-bridge-how-net-calls-java-in-process-205n</guid>
      <description>&lt;p&gt;A lot of Java/.NET integration advice starts with “just make it an API.” That works until the call path is fine-grained, latency-sensitive, or tied to desktop/server code that already runs on the same machine.&lt;/p&gt;

&lt;p&gt;For those cases, shared memory is worth understanding. This walkthrough breaks down what an in-process Java/.NET bridge actually needs: proxy assemblies, runtime DLLs, JVM configuration, Java-side JARs, and the architecture checks that prevent painful startup failures.&lt;/p&gt;




&lt;h2&gt;
  
  
  What We're Building
&lt;/h2&gt;

&lt;p&gt;We'll dissect a .NET application that calls into a Java class using &lt;a href="https://jnbridge.com/software/jnbridgepro" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt; with shared memory. If you need to &lt;strong&gt;call Java from a .NET project&lt;/strong&gt;, understanding these pieces is essential.&lt;/p&gt;

&lt;p&gt;The goal is to make the moving parts visible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How a proxy connects the .NET and Java worlds&lt;/li&gt;
&lt;li&gt;What files live on each side&lt;/li&gt;
&lt;li&gt;Key considerations for shared memory setups&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Switching this project to TCP, including SSL, IP/class whitelisting, and Java-side startup, is covered in the related TCP configuration guide on the JNBridge site.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Project Layout
&lt;/h2&gt;

&lt;p&gt;The directory structure has two main areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;.NET project (&lt;code&gt;BridgeDemo&lt;/code&gt;)&lt;/strong&gt; — the C# application, configuration, and runtime DLLs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Java side&lt;/strong&gt; — the original Java class plus supporting JARs and properties&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of the .NET project as the caller and the Java side as the runtime payload being loaded and invoked.&lt;/p&gt;

&lt;h2&gt;
  
  
  .NET Side: Calling Java with Shared Memory
&lt;/h2&gt;

&lt;p&gt;The .NET side is built around referenced assemblies, supporting DLLs, and an &lt;code&gt;App.config&lt;/code&gt; that defines how the bridge locates Java.&lt;/p&gt;

&lt;h3&gt;
  
  
  Referenced Assemblies
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bridgeDemo.dll&lt;/code&gt; — the generated proxy assembly that exposes the Java class to .NET&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JNBShare.dll&lt;/code&gt; — the core bridge library required for JNBridge communication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are added under &lt;strong&gt;References&lt;/strong&gt; in Visual Studio with &lt;code&gt;Copy Local = True&lt;/code&gt;, so they land in the output folder automatically. That lets &lt;code&gt;BridgeDemo.exe&lt;/code&gt; find them at runtime without extra machine-level setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Supporting DLLs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;JNBSharedMem_x86.dll&lt;/code&gt; / &lt;code&gt;JNBSharedMem_x64.dll&lt;/code&gt; — required only for the Shared Memory transport&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jnbauth_x86.dll&lt;/code&gt; / &lt;code&gt;jnbauth_x64.dll&lt;/code&gt; — used internally by the bridge for authentication and setup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are &lt;strong&gt;not&lt;/strong&gt; referenced in the project. They simply need to be present in the same directory as &lt;code&gt;BridgeDemo.exe&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  App.config
&lt;/h3&gt;

&lt;p&gt;At runtime, &lt;code&gt;BridgeDemo.exe&lt;/code&gt; reads this config to tell JNBridge how to locate and load the Java side:&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;jnbridge&amp;gt;&lt;/span&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-25\bin\server\jvm.dll"&lt;/span&gt;
    &lt;span class="na"&gt;jnbcore=&lt;/span&gt;&lt;span class="s"&gt;"C:\Projects\BridgeDemo\Java Side\jnbcore.jar"&lt;/span&gt;
    &lt;span class="na"&gt;bcel=&lt;/span&gt;&lt;span class="s"&gt;"C:\Projects\BridgeDemo\Java Side\SharedMemory\bcel-6.10.0.jar"&lt;/span&gt;
    &lt;span class="na"&gt;classpath=&lt;/span&gt;&lt;span class="s"&gt;"C:\Projects\BridgeDemo\Java Side;C:\Projects\BridgeDemo\Java Side\javaToCall.jar;"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/jnbridge&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key attributes:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scheme&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transport type: &lt;code&gt;sharedmem&lt;/code&gt; or &lt;code&gt;tcp&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jvm64&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Path to the 64-bit JVM (&lt;code&gt;jvm32&lt;/code&gt; for 32-bit)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jnbcore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Path to &lt;code&gt;jnbcore.jar&lt;/code&gt;, the bridge runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bcel&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Required for Shared Memory; omitted for TCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;classpath&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Where JNBridge finds your Java classes or JARs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The classpath determines whether the .NET side connects to a package directory with &lt;code&gt;.class&lt;/code&gt; files, a JAR, or both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Java Side
&lt;/h2&gt;

&lt;p&gt;The Java side hosts the actual code and bridge runtime that the .NET process connects to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core and Supporting JARs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;jnbcore.jar&lt;/code&gt; — always required; contains the bridge runtime and handles communication&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;bcel-6.10.0.jar&lt;/code&gt; — only needed for Shared Memory transport&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Java Code or Packaged JAR
&lt;/h3&gt;

&lt;p&gt;Depending on your build, you'll have either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A package directory such as &lt;code&gt;BridgeDemo/&lt;/code&gt; with &lt;code&gt;.class&lt;/code&gt; files&lt;/li&gt;
&lt;li&gt;A JAR archive such as &lt;code&gt;BridgeDemo.jar&lt;/code&gt; with compiled classes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Which one gets used depends on the classpath in the &lt;code&gt;.NET&lt;/code&gt; project's &lt;code&gt;App.config&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Java-Side Properties File
&lt;/h3&gt;

&lt;p&gt;The properties file, for example &lt;code&gt;javaSide.properties&lt;/code&gt;, controls how the JVM runs the bridge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;javaSide.serverType&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;sharedmem&lt;/span&gt;
&lt;span class="py"&gt;javaSide.timeout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;10000&lt;/span&gt;
&lt;span class="py"&gt;javaSide.port&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;8085&lt;/span&gt;
&lt;span class="py"&gt;javaSide.useClassWhiteList&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;
&lt;span class="py"&gt;javaSide.classWhiteListFile&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;./classWhiteList.txt&lt;/span&gt;
&lt;span class="py"&gt;javaSide.useSSL&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some entries, like port and SSL, matter more for TCP mode. Keeping them visible in the properties file helps when you later switch transports or compare configurations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Considerations for Shared Memory Setups
&lt;/h2&gt;

&lt;p&gt;Shared Memory provides the fastest .NET↔Java communication because both runtimes share the same process. The tradeoff is that setup details matter more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture Matching
&lt;/h3&gt;

&lt;p&gt;The JVM architecture must match your .NET build target:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;64-bit .NET&lt;/strong&gt; → use 64-bit &lt;code&gt;jvm.dll&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;32-bit .NET&lt;/strong&gt; → use 32-bit &lt;code&gt;jvm.dll&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supporting DLLs must also match:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;JNBSharedMem_x86.dll&lt;/code&gt; / &lt;code&gt;JNBSharedMem_x64.dll&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jnbauth_x86.dll&lt;/code&gt; / &lt;code&gt;jnbauth_x64.dll&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your app targets AnyCPU, include all four DLLs so the bridge can load the correct pair at runtime. For single-architecture builds, you can safely omit the unused set.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep Bridge DLLs Beside the Executable
&lt;/h3&gt;

&lt;p&gt;All bridge DLLs must reside in the same directory as your executable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;JNBShare.dll&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JNBSharedMem_x*.dll&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jnbauth_x*.dll&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No GAC tricks, no hidden machine-wide assumptions — just make the output folder complete.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling &lt;code&gt;ClassNotFoundException&lt;/code&gt; and &lt;code&gt;NoClassDefFoundError&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;These errors during shared-memory startup usually come from one of two issues.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Permissions
&lt;/h4&gt;

&lt;p&gt;Ensure the user running the app has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read &amp;amp; Execute&lt;/li&gt;
&lt;li&gt;List Folder Contents&lt;/li&gt;
&lt;li&gt;Read access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That access needs to cover all Java class and JAR folders, including &lt;code&gt;jnbcore.jar&lt;/code&gt;, &lt;code&gt;bcel-6.x.x.jar&lt;/code&gt;, and any app JARs. Missing access can prevent the JVM from loading classes, especially under ASP.NET or service accounts.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Classloader conflicts on Java 8 and earlier
&lt;/h4&gt;

&lt;p&gt;If permissions check out but errors persist, move the affected &lt;code&gt;.jar&lt;/code&gt; from the regular classpath to the boot classpath:&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;jnbridge&amp;gt;&lt;/span&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-25\bin\server\jvm.dll"&lt;/span&gt;
    &lt;span class="na"&gt;jnbcore=&lt;/span&gt;&lt;span class="s"&gt;"C:\Projects\BridgeDemo\Java Side\jnbcore.jar"&lt;/span&gt;
    &lt;span class="na"&gt;bcel=&lt;/span&gt;&lt;span class="s"&gt;"C:\Projects\BridgeDemo\Java Side\SharedMemory\bcel-6.10.0.jar"&lt;/span&gt;
    &lt;span class="na"&gt;classpath=&lt;/span&gt;&lt;span class="s"&gt;"C:\Projects\BridgeDemo\Java Side;"&lt;/span&gt;
    &lt;span class="na"&gt;jvmOptions.0=&lt;/span&gt;&lt;span class="s"&gt;"-Xbootclasspath/p:C:\Projects\BridgeDemo\Java Side\javaToCall.jar"&lt;/span&gt;
  &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/jnbridge&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;javaToCall.jar&lt;/code&gt; was removed from the classpath and added to the boot classpath, letting the JVM load it earlier.&lt;/p&gt;

&lt;p&gt;Important caveat: &lt;code&gt;-Xbootclasspath/p:&lt;/code&gt; only works through Java 8. It was removed in Java 9+.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shared Memory vs TCP
&lt;/h2&gt;

&lt;p&gt;Shared memory is usually the right fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java and .NET run on the same machine&lt;/li&gt;
&lt;li&gt;Calls are frequent or latency-sensitive&lt;/li&gt;
&lt;li&gt;You want method-level integration rather than coarse-grained service calls&lt;/li&gt;
&lt;li&gt;You can control architecture alignment and deployment layout&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;TCP is usually better when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java and .NET run in separate processes or on separate machines&lt;/li&gt;
&lt;li&gt;You need network-level isolation&lt;/li&gt;
&lt;li&gt;You want SSL, IP whitelisting, or explicit Java-side startup&lt;/li&gt;
&lt;li&gt;Operational flexibility matters more than same-process latency&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Shared memory is the fastest transport option for Java/.NET integration: no network hop, no JSON serialization, and no separate service boundary. The price is stricter runtime alignment and careful file placement.&lt;/p&gt;

&lt;p&gt;If your systems already live on the same machine and you need direct method calls across the Java/.NET boundary, shared memory is hard to beat. If you need process or network separation, use TCP instead.&lt;/p&gt;

&lt;p&gt;Original WordPress version: &lt;a href="https://jnbridge.com/jnbridgepro/call-java-from-dotnet-project-shared-memory-bridge-anatomy" rel="noopener noreferrer"&gt;Call Java from .NET Project: Anatomy of a Shared Memory Bridge&lt;/a&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>interop</category>
    </item>
    <item>
      <title>I Let an AI Set Up My Java/.NET Bridge Project — Here's What Happened</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Fri, 03 Apr 2026 13:09:11 +0000</pubDate>
      <link>https://forem.com/jnbridge/i-let-an-ai-set-up-my-javanet-bridge-project-heres-what-happened-15j0</link>
      <guid>https://forem.com/jnbridge/i-let-an-ai-set-up-my-javanet-bridge-project-heres-what-happened-15j0</guid>
      <description>&lt;p&gt;Setting up a Java/.NET bridge project from scratch is one of those tasks that &lt;em&gt;shouldn't&lt;/em&gt; be hard but somehow always is. Proxy generation, classpath wiring, transport configuration, build scripts — there are enough moving parts that you'll spend your first afternoon just getting "Hello World" to compile.&lt;/p&gt;

&lt;p&gt;So we tried something different: we pointed Claude Code at a reference project, gave it a &lt;code&gt;CLAUDE.md&lt;/code&gt; file documenting every file and build step, and let it handle the wiring. The result was surprisingly good.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Reference Project Does
&lt;/h2&gt;

&lt;p&gt;The project is a .NET Framework console app that calls Java's log4j logging library and a custom Java class through JNBridge-generated proxy DLLs. It's organized into three folders:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Java source&lt;/strong&gt; — your Java classes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy generation tooling&lt;/strong&gt; — JNBridge's proxy generator config&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The .NET project&lt;/strong&gt; — the C# app that calls into Java&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Orchestrator scripts build and run everything in one step.&lt;/p&gt;

&lt;p&gt;The key file is &lt;code&gt;CLAUDE.md&lt;/code&gt;. It documents every file, every build step, and every configuration flag. This means Claude Code understands the full project structure — when you ask it to swap in a different Java library or change the transport mode, it knows exactly which files to touch and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Transport Modes: Pick Your Architecture
&lt;/h2&gt;

&lt;p&gt;The demo ships with two build scripts, each demonstrating a different JNBridge transport mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared Memory Mode (In-Process)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;buildAndRunSharedMem.bat&lt;/code&gt; embeds the JVM directly inside the .NET process. No separate Java process to manage.&lt;/p&gt;

&lt;p&gt;The pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compiles Java source → packages into a JAR&lt;/li&gt;
&lt;li&gt;Generates .NET proxy DLLs (JNBridge scans your JARs and creates C# wrapper classes)&lt;/li&gt;
&lt;li&gt;Builds the C# project with &lt;code&gt;dotnet build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Generates runtime config — injects JVM paths from &lt;code&gt;env.bat&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Runs the demo&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the fastest option — no network overhead, no serialization. Your .NET code calls Java methods like they're native .NET calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  TCP Mode (Separate Process)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;buildAndRunTCP.bat&lt;/code&gt; runs Java as a separate process and connects over TCP (port 8085). Useful when you need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debug the Java side independently&lt;/li&gt;
&lt;li&gt;Run Java on a different machine&lt;/li&gt;
&lt;li&gt;Isolate the JVM from the .NET process&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same build pipeline, but it swaps in TCP config, launches the Java side in a separate console window, and cleans up when the demo exits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Install JNBridgePro
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://jnbridge.com/download/download-jnbridgepro" rel="noopener noreferrer"&gt;Download JNBridgePro&lt;/a&gt; and run the installer.&lt;/p&gt;

&lt;p&gt;Then grab the &lt;a href="https://jnbridge.com/downloads/LoggerDemoNew_Net4.8ToJava.zip" rel="noopener noreferrer"&gt;demo project with CLAUDE.md&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Get a License
&lt;/h3&gt;

&lt;p&gt;After installation, run the JNBridge registration tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Registration Key&lt;/strong&gt; → copy registration key&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;"Request License"&lt;/strong&gt; to complete the online request&lt;/li&gt;
&lt;li&gt;License arrives via email — drop it in the build directory&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Edit One File
&lt;/h3&gt;

&lt;p&gt;Open &lt;code&gt;env.bat&lt;/code&gt; at the project root and set your Java home:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;set&lt;/span&gt; &lt;span class="kd"&gt;JAVA_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kd"&gt;C&lt;/span&gt;:\Program &lt;span class="kd"&gt;Files&lt;/span&gt;\Java\jdk1.8.0_202
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. All build scripts read their Java paths from this one file.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Run It
&lt;/h3&gt;

&lt;p&gt;Double-click &lt;code&gt;buildAndRunSharedMem.bat&lt;/code&gt;. If everything's set up correctly, you'll see .NET calling Java.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Part: Why CLAUDE.md Matters
&lt;/h2&gt;

&lt;p&gt;This is where the project gets interesting for the Dev.to crowd.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;CLAUDE.md&lt;/code&gt; file is essentially a machine-readable project handbook. Open Claude Code in the project directory and tell it what you want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"Swap log4j for my JDBC driver"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Add a new Java class to the proxies"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Switch to TCP mode"&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Claude Code reads the documentation and knows the full build pipeline, so it can make the right changes across all the files without you having to trace through the wiring yourself.&lt;/p&gt;

&lt;p&gt;This pattern — &lt;strong&gt;documenting your project structure in a way that AI tools can consume&lt;/strong&gt; — is something I think we'll see a lot more of. It's not just about Java/.NET integration; any project with complex build pipelines benefits from having a &lt;code&gt;CLAUDE.md&lt;/code&gt; (or equivalent) that maps out the dependency graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://jnbridge.com/download/download-jnbridgepro" rel="noopener noreferrer"&gt;Download JNBridgePro&lt;/a&gt; (free trial)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jnbridge.com/downloads/LoggerDemoNew_Net4.8ToJava.zip" rel="noopener noreferrer"&gt;Grab the demo project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jnbridge.com/software/jnbridgepro/developer-center/overview" rel="noopener noreferrer"&gt;Developer Center&lt;/a&gt; for more guides&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're working on Java/.NET interop — or just curious about using AI to manage complex project setups — give it a spin and let me know how it goes in the comments.&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Calling Java from VB.NET: Every Option from REST to In-Process Bridging</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Mon, 30 Mar 2026 13:37:45 +0000</pubDate>
      <link>https://forem.com/jnbridge/calling-java-from-vbnet-every-option-from-rest-to-in-process-bridging-2mk4</link>
      <guid>https://forem.com/jnbridge/calling-java-from-vbnet-every-option-from-rest-to-in-process-bridging-2mk4</guid>
      <description>&lt;p&gt;If you've ever Googled "call Java from VB.NET" and gotten nothing but abandoned StackOverflow threads from 2014, you're not alone. VB.NET is quietly running millions of enterprise apps — financial platforms, healthcare systems, logistics — and those teams increasingly need to tap into Java libraries they can't rewrite.&lt;/p&gt;

&lt;p&gt;I spent a week mapping out every approach that actually works in 2026. Here's what I found.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Two Runtimes, No Shared Anything
&lt;/h2&gt;

&lt;p&gt;VB.NET runs on the .NET CLR. Java runs on the JVM. They don't share memory, type systems, or calling conventions. If you need &lt;code&gt;Apache PDFBox&lt;/code&gt; for PDF generation, or a proprietary Java pricing engine your company spent years building, you can't just &lt;code&gt;Imports com.acme.pricing&lt;/code&gt; and go.&lt;/p&gt;

&lt;p&gt;Or can you?&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: REST/HTTP APIs
&lt;/h2&gt;

&lt;p&gt;The obvious approach — wrap your Java code in a Spring Boot service, call it via &lt;code&gt;HttpClient&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Language-agnostic, works across networks, everyone understands HTTP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; 2-15ms per call. JSON serialization overhead. You need a separate running Java service. Fine for occasional calls, painful when you're making thousands per second.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vb"&gt;&lt;code&gt;&lt;span class="c1"&gt;' The REST approach — works but adds overhead&lt;/span&gt;
&lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;client&lt;/span&gt; &lt;span class="ow"&gt;As&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="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&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="n"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:8080/api/price?product=ABC"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&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;ReadAsStringAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Option 2: gRPC
&lt;/h2&gt;

&lt;p&gt;Binary protocol, Protocol Buffers, lower latency than REST.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; ~0.5-2ms per call, strongly typed, streaming support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Still needs a separate Java process. Protobuf schema management. VB.NET gRPC support exists but isn't as polished as C#'s.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: Message Queues
&lt;/h2&gt;

&lt;p&gt;Publish/subscribe through RabbitMQ or Kafka. Async by nature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Decoupled, scalable, resilient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Not suitable for synchronous request/response. More infrastructure to manage. Overkill when you just need to call a method and get a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 4: In-Process Bridge (the one I'd pick)
&lt;/h2&gt;

&lt;p&gt;A bridge like &lt;a href="https://jnbridge.com/jnbridgepro" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt; loads the JVM directly into your .NET process and generates .NET proxy classes for Java objects. Your VB.NET code calls Java methods with &lt;strong&gt;native syntax&lt;/strong&gt; — no HTTP, no serialization, no separate service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vb"&gt;&lt;code&gt;&lt;span class="c1"&gt;' Java's HashMap, used natively from VB.NET&lt;/span&gt;
&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;java.util&lt;/span&gt;

&lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;map&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&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;"value"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not pseudocode. That actually compiles and runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Code: PDFBox from VB.NET
&lt;/h2&gt;

&lt;p&gt;Apache PDFBox is Java's go-to PDF library. With proxy classes generated, VB.NET uses it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vb"&gt;&lt;code&gt;&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;org.apache.pdfbox.pdmodel&lt;/span&gt;
&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;org.apache.pdfbox.pdmodel.font&lt;/span&gt;

&lt;span class="k"&gt;Public&lt;/span&gt; &lt;span class="k"&gt;Sub&lt;/span&gt; &lt;span class="nf"&gt;CreatePDF&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;document&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;PDDocument&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;PDPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;contentStream&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;PDPageContentStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;contentStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setFont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PDType1Font&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HELVETICA_BOLD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;contentStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beginText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;contentStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newLineAtOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;contentStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;showText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Generated from VB.NET via JNBridgePro"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;contentStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;contentStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"C:\output\report.pdf"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;End&lt;/span&gt; &lt;span class="k"&gt;Sub&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No REST endpoint. No separate service. Just call the Java API like it's a .NET library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Code: Apache Kafka Producer
&lt;/h2&gt;

&lt;p&gt;Instead of hunting for a third-party .NET Kafka client, use the official Java client directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vb"&gt;&lt;code&gt;&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;org.apache.kafka.clients.producer&lt;/span&gt;
&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;java.util&lt;/span&gt;

&lt;span class="k"&gt;Public&lt;/span&gt; &lt;span class="k"&gt;Sub&lt;/span&gt; &lt;span class="nf"&gt;SendKafkaMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt; &lt;span class="ow"&gt;As&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;message&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;props&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"bootstrap.servers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"kafka-broker:9092"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"key.serializer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;
        &lt;span class="s"&gt;"org.apache.kafka.common.serialization.StringSerializer"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"value.serializer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;
        &lt;span class="s"&gt;"org.apache.kafka.common.serialization.StringSerializer"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;producer&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;KafkaProducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;record&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;ProducerRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&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="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;End&lt;/span&gt; &lt;span class="k"&gt;Sub&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real Code: Proprietary Java Business Logic
&lt;/h2&gt;

&lt;p&gt;The most common scenario — your org has a Java pricing engine or risk model that took years to build. Rewriting it in VB.NET isn't realistic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vb"&gt;&lt;code&gt;&lt;span class="k"&gt;Imports&lt;/span&gt; &lt;span class="nn"&gt;com.acme.pricing&lt;/span&gt;

&lt;span class="k"&gt;Public&lt;/span&gt; &lt;span class="k"&gt;Function&lt;/span&gt; &lt;span class="nf"&gt;CalculatePrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt; &lt;span class="ow"&gt;As&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;quantity&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="kt"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="kt"&gt;Decimal&lt;/span&gt;
    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;engine&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;PricingEngine&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="n"&gt;loadRules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pricing-rules-2026.xml"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;request&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;PriceRequest&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;setProductId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&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;setQuantity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quantity&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;setCurrency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"USD"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="n"&gt;PriceResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&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;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;Return&lt;/span&gt; &lt;span class="k"&gt;CDec&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;getTotalPrice&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;End&lt;/span&gt; &lt;span class="k"&gt;Function&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Comparison
&lt;/h2&gt;

&lt;p&gt;For apps making frequent cross-runtime calls, this matters:&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;Latency/Call&lt;/th&gt;
&lt;th&gt;Throughput&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;REST/HTTP&lt;/td&gt;
&lt;td&gt;2-15ms&lt;/td&gt;
&lt;td&gt;~500-2K/sec&lt;/td&gt;
&lt;td&gt;Loose coupling, cross-network&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gRPC&lt;/td&gt;
&lt;td&gt;0.5-3ms&lt;/td&gt;
&lt;td&gt;~2K-10K/sec&lt;/td&gt;
&lt;td&gt;High-throughput microservices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In-process bridge (shared mem)&lt;/td&gt;
&lt;td&gt;0.01-0.1ms&lt;/td&gt;
&lt;td&gt;~50K-500K/sec&lt;/td&gt;
&lt;td&gt;Tight integration, real-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;In-process bridge (TCP)&lt;/td&gt;
&lt;td&gt;0.1-1ms&lt;/td&gt;
&lt;td&gt;~5K-50K/sec&lt;/td&gt;
&lt;td&gt;Cross-process deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The shared-memory bridge is &lt;strong&gt;100-1000x faster&lt;/strong&gt; than REST for individual calls. Negligible for occasional calls — transformative when your VB.NET app is doing real-time pricing or batch data processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture Pattern: Wrapper Service Layer
&lt;/h2&gt;

&lt;p&gt;My recommendation — create a VB.NET wrapper class that isolates all Java calls. The rest of your app gets a clean .NET API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vb"&gt;&lt;code&gt;&lt;span class="k"&gt;Public&lt;/span&gt; &lt;span class="k"&gt;Class&lt;/span&gt; &lt;span class="nc"&gt;JavaPricingService&lt;/span&gt;
    &lt;span class="k"&gt;Private&lt;/span&gt; &lt;span class="n"&gt;_engine&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="n"&gt;PricingEngine&lt;/span&gt;

    &lt;span class="k"&gt;Public&lt;/span&gt; &lt;span class="k"&gt;Sub&lt;/span&gt; &lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;_engine&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;PricingEngine&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="n"&gt;loadRules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pricing-rules-2026.xml"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;End&lt;/span&gt; &lt;span class="k"&gt;Sub&lt;/span&gt;

    &lt;span class="k"&gt;Public&lt;/span&gt; &lt;span class="k"&gt;Function&lt;/span&gt; &lt;span class="nf"&gt;GetPrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&lt;/span&gt; &lt;span class="ow"&gt;As&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;qty&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="kt"&gt;Integer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="kt"&gt;Decimal&lt;/span&gt;
        &lt;span class="k"&gt;Dim&lt;/span&gt; &lt;span class="nv"&gt;request&lt;/span&gt; &lt;span class="ow"&gt;As&lt;/span&gt; &lt;span class="k"&gt;New&lt;/span&gt; &lt;span class="n"&gt;PriceRequest&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;setProductId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;productId&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;setQuantity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;Return&lt;/span&gt; &lt;span class="k"&gt;CDec&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="n"&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;getTotalPrice&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;End&lt;/span&gt; &lt;span class="k"&gt;Function&lt;/span&gt;
&lt;span class="k"&gt;End&lt;/span&gt; &lt;span class="k"&gt;Class&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application code never needs to know it's talking to Java under the hood.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mixed Teams? Same Proxies Work in C
&lt;/h2&gt;

&lt;p&gt;If your team uses both VB.NET and C#, the proxy assembly is language-agnostic:&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;// Same Kafka example in C# — same proxy DLL&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;props&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;Properties&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;props&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;"bootstrap.servers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"kafka-broker:9092"&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;producer&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;KafkaProducer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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;ProducerRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&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="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate once, use from any .NET language.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch Out For
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JVM Memory&lt;/strong&gt; — The JVM runs inside your .NET process. Set &lt;code&gt;-Xmx&lt;/code&gt; appropriately or you'll get mysterious OOM crashes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Thread Safety&lt;/strong&gt; — Java objects follow Java's threading model. If the Java library isn't thread-safe, use &lt;code&gt;SyncLock&lt;/code&gt; or per-thread instances on the VB.NET side.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Classpath Issues&lt;/strong&gt; — The #1 setup problem. Use &lt;code&gt;jdeps&lt;/code&gt; to find transitive dependencies you're missing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Exception Translation&lt;/strong&gt; — Java exceptions become .NET exceptions through the bridge. Catch &lt;code&gt;JNBException&lt;/code&gt; for bridge errors, original Java types for app errors.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://jnbridge.com/download" rel="noopener noreferrer"&gt;Download JNBridgePro&lt;/a&gt; (free evaluation)&lt;/li&gt;
&lt;li&gt;Generate proxies for your Java classes&lt;/li&gt;
&lt;li&gt;Add references to your VB.NET project&lt;/li&gt;
&lt;li&gt;Write code using native VB.NET syntax&lt;/li&gt;
&lt;li&gt;Deploy — both JVM and CLR must be present on target&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Works with .NET Framework &lt;em&gt;and&lt;/em&gt; .NET Core/.NET 8/9. If you're migrating from Framework to modern .NET, your Java integration code carries over.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have questions about VB.NET + Java integration? Drop them in the comments — I'll answer from production experience.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>java</category>
      <category>vbnet</category>
      <category>interop</category>
    </item>
    <item>
      <title>JNBridgePro vs IKVM vs Javonet: Java/.NET Bridge Comparison (2026)</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Sat, 28 Mar 2026 13:05:01 +0000</pubDate>
      <link>https://forem.com/jnbridge/jnbridgepro-vs-ikvm-vs-javonet-javanet-bridge-comparison-2026-phh</link>
      <guid>https://forem.com/jnbridge/jnbridgepro-vs-ikvm-vs-javonet-javanet-bridge-comparison-2026-phh</guid>
      <description>&lt;p&gt;I've been deep in the Java/.NET integration space for years, and the one question that comes up more than anything: &lt;strong&gt;which bridge tool should I actually use?&lt;/strong&gt; IKVM? JNBridgePro? Javonet? Something else entirely?&lt;/p&gt;

&lt;p&gt;The answer depends on your Java version, deployment model, and how much you value long-term support. Here's an honest breakdown of all three — plus alternatives you should know about.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Compare Java/.NET Bridges?
&lt;/h2&gt;

&lt;p&gt;Java/.NET bridges solve a common enterprise problem: your organization runs both Java and .NET codebases, and they need to communicate. Instead of rewriting code or building REST/gRPC wrappers, a bridge enables direct method calls between the two runtimes.&lt;/p&gt;

&lt;p&gt;But bridges differ dramatically in architecture, performance characteristics, Java version support, and long-term viability. Choosing the wrong one can mean a forced migration later — exactly the kind of disruption you're trying to avoid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Comparison Table
&lt;/h2&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;JNBridgePro&lt;/th&gt;
&lt;th&gt;IKVM&lt;/th&gt;
&lt;th&gt;Javonet&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;In-process bridge (JVM + CLR)&lt;/td&gt;
&lt;td&gt;Bytecode translation (Java → CIL)&lt;/td&gt;
&lt;td&gt;Cross-runtime invocation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Java Version Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Java 8–21+&lt;/td&gt;
&lt;td&gt;Java SE 8 only&lt;/td&gt;
&lt;td&gt;Java 8+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;.NET Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Framework + Core/.NET 8/9&lt;/td&gt;
&lt;td&gt;Framework, partial Core&lt;/td&gt;
&lt;td&gt;Framework + Core&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Uses Real JVM?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (translates to .NET)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Latency per Call&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Microseconds&lt;/td&gt;
&lt;td&gt;Zero (native .NET)&lt;/td&gt;
&lt;td&gt;Low (in-process)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dynamic Class Loading&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full support&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reflection Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Full&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Commercial Support&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Professional (since 2001)&lt;/td&gt;
&lt;td&gt;None (community)&lt;/td&gt;
&lt;td&gt;Commercial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Linux / Docker / K8s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Yes&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;Commercial (free eval)&lt;/td&gt;
&lt;td&gt;Open source (MIT)&lt;/td&gt;
&lt;td&gt;Commercial ($69/mo+)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;First Release&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2001&lt;/td&gt;
&lt;td&gt;2004&lt;/td&gt;
&lt;td&gt;2015&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Languages Supported&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Java + .NET&lt;/td&gt;
&lt;td&gt;Java → .NET only&lt;/td&gt;
&lt;td&gt;6+ languages&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  JNBridgePro: In-Depth Review
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;JNBridgePro runs a real JVM alongside the .NET CLR, either in the same process (shared memory) or connected via TCP. A proxy generation tool inspects your Java JARs and creates matching .NET proxy classes. Your C# or VB.NET code calls these proxy methods with native syntax — the bridge handles type conversion, exception marshaling, and memory management transparently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full Java compatibility&lt;/strong&gt; — Runs a real JVM, so any Java library, framework, or feature works. Virtual threads, records, pattern matching, dynamic proxies — all supported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mature and battle-tested&lt;/strong&gt; — In production since 2001. Used by Fortune 500 companies in financial services, healthcare, manufacturing, and government.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Professional support&lt;/strong&gt; — Guaranteed response times, dedicated engineering support, compatibility updates for new Java and .NET versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flexible deployment&lt;/strong&gt; — Shared memory (lowest latency), TCP (separate machines), Docker containers, Kubernetes pods.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low integration effort&lt;/strong&gt; — Proxy generation is automated. Most integrations go from evaluation to production in weeks, not months.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Commercial license required (free evaluation available)&lt;/li&gt;
&lt;li&gt;JVM overhead (typically 256MB–1GB heap)&lt;/li&gt;
&lt;li&gt;Cross-runtime call overhead in microseconds — only matters in extremely tight loops&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best For
&lt;/h3&gt;

&lt;p&gt;Enterprise teams that need reliable, long-term Java/.NET integration with professional support. Especially strong for modern Java (11+), containerized deployments, and compliance-grade reliability.&lt;/p&gt;

&lt;h2&gt;
  
  
  IKVM: In-Depth Review
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;IKVM translates compiled Java bytecode (.class and .jar files) into .NET CIL assemblies. The translated Java code runs directly on the CLR as native .NET code — no JVM needed at runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero cross-runtime overhead&lt;/strong&gt; — After translation, Java code IS .NET code. Native CLR calls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No JVM dependency at runtime&lt;/strong&gt; — Simplifies deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open source (MIT license)&lt;/strong&gt; — Free for any use, including commercial.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple for simple cases&lt;/strong&gt; — Self-contained Java libraries can work well.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Java SE 8 only&lt;/strong&gt; — Cannot use Java 9+ features (modules, records, virtual threads, sealed classes). This is the critical limitation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incomplete API coverage&lt;/strong&gt; — Libraries depending on JVM internals, custom class loaders, or reflection-heavy frameworks may fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No commercial support&lt;/strong&gt; — Community-maintained with periods of dormancy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No dynamic class loading&lt;/strong&gt; — Frameworks like Spring may not work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best For
&lt;/h3&gt;

&lt;p&gt;Projects using simple, self-contained Java 8 libraries where open-source licensing is required and the limitations are acceptable. Not recommended for enterprise production systems needing long-term support.&lt;/p&gt;

&lt;h2&gt;
  
  
  Javonet: In-Depth Review
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;Javonet provides a cross-runtime invocation framework supporting Java, .NET, Python, Ruby, Perl, and Node.js. It uses an invoke-based API where you specify class names and method names as strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multi-language support&lt;/strong&gt; — Covers 6+ languages with one tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Modern Java support&lt;/strong&gt; — Works with current Java versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commercial support available&lt;/strong&gt; — Paid plans with SLA options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud and container friendly&lt;/strong&gt; — Modern deployment patterns supported.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invoke-based API&lt;/strong&gt; — You call methods by string name rather than typed proxy classes. No compile-time type checking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generic approach&lt;/strong&gt; — May not be as deeply optimized for Java/.NET as a purpose-built bridge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription pricing&lt;/strong&gt; — Starts at $69/month, scales with usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Younger product&lt;/strong&gt; — Founded 2015 vs JNBridgePro's 2001.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best For
&lt;/h3&gt;

&lt;p&gt;Teams in polyglot environments where Java/.NET is just one of several language integration needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Alternatives Worth Knowing
&lt;/h2&gt;

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

&lt;p&gt;Wrap Java code in a web service and call from .NET via HTTP. Adds 5–50ms latency per call but is language-independent and well-understood. Best for distributed systems where Java and .NET already run on different machines.&lt;/p&gt;

&lt;h3&gt;
  
  
  gRPC
&lt;/h3&gt;

&lt;p&gt;High-performance RPC with Protobuf serialization. 1–10ms latency with strong typing through &lt;code&gt;.proto&lt;/code&gt; files. Best for high-throughput service-to-service communication.&lt;/p&gt;

&lt;h3&gt;
  
  
  GraalVM Native Image
&lt;/h3&gt;

&lt;p&gt;Compile Java to a native shared library, load from .NET via P/Invoke. Experimental, with severe restrictions on reflection and dynamic class loading.&lt;/p&gt;

&lt;h3&gt;
  
  
  JNI + P/Invoke (Manual Bridge)
&lt;/h3&gt;

&lt;p&gt;Maximum control but enormous development effort. Only realistic for very narrow integration surfaces with deep C++ expertise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Benchmarks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;JNBridgePro&lt;/th&gt;
&lt;th&gt;IKVM&lt;/th&gt;
&lt;th&gt;Javonet&lt;/th&gt;
&lt;th&gt;REST&lt;/th&gt;
&lt;th&gt;gRPC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Single call latency&lt;/td&gt;
&lt;td&gt;1–50µs&lt;/td&gt;
&lt;td&gt;0 (native)&lt;/td&gt;
&lt;td&gt;Low µs&lt;/td&gt;
&lt;td&gt;5–50ms&lt;/td&gt;
&lt;td&gt;1–10ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput (calls/sec)&lt;/td&gt;
&lt;td&gt;100K+&lt;/td&gt;
&lt;td&gt;Native .NET&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;1–10K&lt;/td&gt;
&lt;td&gt;10–50K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory overhead&lt;/td&gt;
&lt;td&gt;JVM heap (256MB–1GB)&lt;/td&gt;
&lt;td&gt;Minimal&lt;/td&gt;
&lt;td&gt;Runtime overhead&lt;/td&gt;
&lt;td&gt;Separate process&lt;/td&gt;
&lt;td&gt;Separate process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Startup time&lt;/td&gt;
&lt;td&gt;JVM init (1–3s)&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Runtime init&lt;/td&gt;
&lt;td&gt;Service startup&lt;/td&gt;
&lt;td&gt;Service startup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Key insight:&lt;/strong&gt; IKVM wins on raw per-call performance because there's no cross-runtime boundary. But IKVM's Java 8 limitation means you're trading performance for compatibility. For most enterprise workloads, JNBridgePro's microsecond overhead is negligible compared to the business logic and I/O surrounding each call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision Framework
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Choose JNBridgePro if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You use Java 9+ (Java 11, 17, 21)&lt;/li&gt;
&lt;li&gt;You need professional support with SLAs&lt;/li&gt;
&lt;li&gt;Your Java code uses dynamic class loading, reflection, or complex frameworks&lt;/li&gt;
&lt;li&gt;You're deploying in Docker/Kubernetes&lt;/li&gt;
&lt;li&gt;Long-term reliability and vendor stability matter&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choose IKVM if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your Java code targets Java SE 8 exclusively&lt;/li&gt;
&lt;li&gt;The library is simple and self-contained&lt;/li&gt;
&lt;li&gt;You need zero cross-runtime overhead&lt;/li&gt;
&lt;li&gt;Open-source licensing is required&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choose Javonet if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to integrate more than just Java and .NET&lt;/li&gt;
&lt;li&gt;The invoke-based API works for your use case&lt;/li&gt;
&lt;li&gt;Subscription pricing fits your budget&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choose REST/gRPC if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Java and .NET run on different machines&lt;/li&gt;
&lt;li&gt;Call frequency is low (&amp;lt; 100 calls/sec)&lt;/li&gt;
&lt;li&gt;You want complete language independence&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Which Java/.NET bridge is the most popular?&lt;/strong&gt;&lt;br&gt;
JNBridgePro has the longest track record (since 2001) and the largest enterprise user base. IKVM has the most open-source downloads but limited active usage due to its Java 8 restriction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use IKVM and JNBridgePro together?&lt;/strong&gt;&lt;br&gt;
Technically yes, but not recommended. Pick the approach that best fits your Java version and requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a free Java/.NET bridge for production?&lt;/strong&gt;&lt;br&gt;
IKVM is free (MIT), but limited to Java SE 8. JNBridgePro offers a free evaluation — production requires a commercial license.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long does integration take?&lt;/strong&gt;&lt;br&gt;
JNBridgePro: typically 1–2 days including proxy generation. IKVM: hours for simple JARs, but translation issues can take weeks. Javonet: 1–2 days with more verbose code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migrating from IKVM?&lt;/strong&gt;&lt;br&gt;
See the &lt;a href="https://jnbridge.com/jnbridgepro/migrating-ikvm-to-jnbridgepro" rel="noopener noreferrer"&gt;IKVM to JNBridgePro migration guide&lt;/a&gt;. Typical migration: 2–4 weeks for medium-sized applications.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://jnbridge.com/jnbridgepro/jnbridgepro-vs-ikvm-vs-javonet-comparison" rel="noopener noreferrer"&gt;jnbridge.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>interop</category>
    </item>
    <item>
      <title>JNBridgePro v12.1: .NET 10, JDK 25, and AI-Assisted Configuration Are Here</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Mon, 23 Mar 2026 16:53:15 +0000</pubDate>
      <link>https://forem.com/jnbridge/jnbridgepro-v121-net-10-jdk-25-and-ai-assisted-configuration-are-here-4k4j</link>
      <guid>https://forem.com/jnbridge/jnbridgepro-v121-net-10-jdk-25-and-ai-assisted-configuration-are-here-4k4j</guid>
      <description>&lt;p&gt;If you've ever wired Java and .NET together in production, you know the runtime version treadmill never stops. Microsoft ships .NET annually, Java's on a six-month cadence, and your integration layer needs to keep up or become the bottleneck.&lt;/p&gt;

&lt;p&gt;JNBridgePro v12.1 just dropped, and it's the first release to fully support .NET 10 alongside JDK 25 — plus something genuinely clever: a demo folder designed for AI coding assistants.&lt;/p&gt;

&lt;p&gt;Here's what's actually in the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  .NET 8, 9, and 10 — All Supported
&lt;/h2&gt;

&lt;p&gt;v12.1 generates proxies that work natively across .NET 8 (current LTS), .NET 9, and .NET 10 — on both Windows and 64-bit Linux. No conditional compilation, no separate builds per target framework.&lt;/p&gt;

&lt;p&gt;This matters because if you're bridging Java libraries into a .NET app, you don't want your integration tooling to be the reason you can't upgrade runtimes. With v12.1, you upgrade .NET, regenerate proxies, done.&lt;/p&gt;

&lt;h2&gt;
  
  
  JDK 8 Through 25 + Jakarta EE 11
&lt;/h2&gt;

&lt;p&gt;On the Java side, full coverage from JDK 8 all the way to JDK 25, plus Java EE 8 through Jakarta EE 11. That's the entire range from legacy Java 8 monoliths to brand-new Jakarta EE deployments — no version gaps.&lt;/p&gt;

&lt;p&gt;If you're maintaining a Java 8 app that talks to a .NET 10 service (or vice versa), this is the kind of compatibility range that means you don't have to synchronize your upgrade schedules across stacks.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-Assisted Configuration — The Demo Folder
&lt;/h2&gt;

&lt;p&gt;This is the clever addition. v12.1 ships with a demo folder containing a README and all the configuration files in the correct places. Hand that folder to Claude Code, Copilot, or any AI coding agent with file access, and it instantly knows how to configure JNBridgePro for your system. You can also paste the README into ChatGPT for guidance.&lt;/p&gt;

&lt;p&gt;The idea is dead simple: instead of reading through setup docs, you give your AI coding assistant the demo folder and go from zero to configured in minutes. The folder covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Proxy generation configs&lt;/strong&gt; — correctly structured so an AI agent can adapt them to your Java classes and .NET targets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TCP/binary and shared memory communication&lt;/strong&gt; — reference patterns an AI can scaffold your transport layer from&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment patterns&lt;/strong&gt; — common enterprise scenarios ready for an AI assistant to adapt to your environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Configuration is where most integration time gets burned, not the actual coding. Having a folder that an AI assistant can immediately parse and work from cuts that setup time dramatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What JNBridgePro Actually Does (Quick Refresher)
&lt;/h2&gt;

&lt;p&gt;For those who haven't used it: JNBridgePro generates proxy classes that let you call Java from C# (or C# from Java) using native syntax. No REST wrappers, no message queues, no serialization layers — direct in-process or TCP-based method calls.&lt;/p&gt;

&lt;p&gt;You point it at Java classes, it generates .NET proxies. You call those proxies like regular C# objects. Under the hood, it handles marshaling, type conversion, and communication. It's been the go-to for enterprise Java/.NET integration for 20+ years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Should Care
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Teams running mixed Java/.NET stacks&lt;/strong&gt; who need to stay current on both runtimes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anyone on .NET 9/10&lt;/strong&gt; who needs Java library access without downgrading&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shops using AI coding assistants&lt;/strong&gt; — hand the demo folder to Claude Code or Copilot and go from zero to configured&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legacy Java 8 maintainers&lt;/strong&gt; bridging into modern .NET — full compatibility, no workarounds&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://jnbridge.com/download/download-jnbridgepro" rel="noopener noreferrer"&gt;Download JNBridgePro v12.1&lt;/a&gt; — free evaluation, no credit card.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jnbridge.com/guides/releasenotes.pdf" rel="noopener noreferrer"&gt;Full release notes (PDF)&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's your team's Java/.NET integration setup look like? Still hand-rolling REST wrappers, or using something more direct? Curious what approaches people are running in production.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>release</category>
    </item>
    <item>
      <title>Java + .NET Integration Broke? Here's Your Fix-It Checklist</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Fri, 20 Mar 2026 13:06:40 +0000</pubDate>
      <link>https://forem.com/jnbridge/java-net-integration-broke-heres-your-fix-it-checklist-18nl</link>
      <guid>https://forem.com/jnbridge/java-net-integration-broke-heres-your-fix-it-checklist-18nl</guid>
      <description>&lt;p&gt;If you've ever stared at a &lt;code&gt;ClassNotFoundException&lt;/code&gt; wrapped inside a &lt;code&gt;TypeLoadException&lt;/code&gt; wrapped inside a &lt;code&gt;TimeoutException&lt;/code&gt; — welcome to cross-runtime debugging.&lt;/p&gt;

&lt;p&gt;I've spent a lot of time troubleshooting Java/.NET integration issues, and the pattern is always the same: the error message points you to one runtime while the actual problem lives in the other (or in the configuration between them). This guide covers the errors I see most often, with specific fixes for each.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Start: The Systematic Approach
&lt;/h2&gt;

&lt;p&gt;Before diving into specific errors, follow this process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identify which runtime throws the error&lt;/strong&gt; — Is it a Java exception wrapped in .NET, a .NET exception, or a bridge/communication error?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the full stack trace&lt;/strong&gt; — Cross-runtime stack traces include both Java and .NET frames. The root cause is usually in the innermost exception.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reproduce in isolation&lt;/strong&gt; — Can you call the Java method directly? Can you call a simple bridge method? This isolates whether the issue is in Java code, .NET code, or the integration layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check versions&lt;/strong&gt; — Ensure JDK version, .NET version, and bridge version are compatible.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  ClassNotFoundException and NoClassDefFoundError
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ClassNotFoundException&lt;/span&gt;&lt;span class="o"&gt;:&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;company&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;MyService&lt;/span&gt;
&lt;span class="c1"&gt;// or&lt;/span&gt;
&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;NoClassDefFoundError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nc"&gt;MyService&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  1. Missing JAR in Classpath
&lt;/h3&gt;

&lt;p&gt;The most common cause. The Java class exists in a JAR that isn't on the JVM's classpath when the bridge loads it.&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;// Fix: Add the JAR to the classpath configuration&lt;/span&gt;
&lt;span class="c1"&gt;// JNBridgePro: Add to classpath in your .jnbproperties file&lt;/span&gt;
&lt;span class="n"&gt;classpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nl"&gt;C:&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;myapp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jar&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;&lt;span class="nl"&gt;C:&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;dependency&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jar&lt;/span&gt;

&lt;span class="c1"&gt;// REST/gRPC: Ensure the JAR is in the service's classpath&lt;/span&gt;
&lt;span class="n"&gt;java&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="s"&gt;"myapp.jar:libs/*"&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;company&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Transitive Dependency Missing
&lt;/h3&gt;

&lt;p&gt;Your JAR loads fine, but it depends on another JAR that's missing.&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;# Diagnostic: Check what the class needs&lt;/span&gt;
jar tf myapp.jar | &lt;span class="nb"&gt;grep &lt;/span&gt;MyService    &lt;span class="c"&gt;# Verify class exists&lt;/span&gt;
jdeps myapp.jar                       &lt;span class="c"&gt;# Show dependencies&lt;/span&gt;

&lt;span class="c"&gt;# Fix: Add all transitive dependencies&lt;/span&gt;
&lt;span class="c"&gt;# Maven: mvn dependency:copy-dependencies -DoutputDirectory=./libs&lt;/span&gt;
&lt;span class="c"&gt;# Gradle: Copy all runtime dependencies to a flat directory&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. ClassNotFoundException vs NoClassDefFoundError
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ClassNotFoundException:&lt;/strong&gt; The class was never found. Check classpath.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NoClassDefFoundError:&lt;/strong&gt; The class was found during compilation but not at runtime, OR a static initializer failed. Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static blocks in the Java class — if they throw exceptions, the class becomes permanently unavailable&lt;/li&gt;
&lt;li&gt;Different JDK versions between compile-time and runtime&lt;/li&gt;
&lt;li&gt;JAR file corruption (re-download or rebuild)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  TypeLoadException and Type Mismatch Errors
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TypeLoadException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Could&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;load&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;JavaProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MyService&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;span class="c1"&gt;// or&lt;/span&gt;
&lt;span class="n"&gt;InvalidCastException&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Unable&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;cast&lt;/span&gt; &lt;span class="err"&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;util&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Proxy Class Out of Date
&lt;/h3&gt;

&lt;p&gt;The .NET proxy was generated from a different version of the Java class. After any Java API change, &lt;strong&gt;regenerate your proxy classes&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Java-to-.NET Type Mapping Gotchas
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Java Type&lt;/th&gt;
&lt;th&gt;.NET Type&lt;/th&gt;
&lt;th&gt;Watch Out For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;java.lang.Long&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;long&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Null Long → .NET can't unbox null to value type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;java.util.Date&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DateTime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timezone conversion mismatches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;java.math.BigDecimal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;decimal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Precision differences (Java arbitrary, .NET 28-29 digits)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;java.util.List&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IList&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Generic type erasure in Java&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;byte[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;byte[]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Java bytes are signed (-128 to 127), .NET unsigned (0 to 255)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Generic Type Erasure
&lt;/h3&gt;

&lt;p&gt;Java erases generic types at runtime. A &lt;code&gt;List&amp;lt;String&amp;gt;&lt;/code&gt; and &lt;code&gt;List&amp;lt;Integer&amp;gt;&lt;/code&gt; are both just &lt;code&gt;List&lt;/code&gt; at the JVM level.&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;// Problem: Java method returns List&amp;lt;Customer&amp;gt;, but bridge sees raw List&lt;/span&gt;
&lt;span class="c1"&gt;// Fix: Cast elements individually on .NET side&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javaProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCustomers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cast&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CustomerProxy&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Memory Leaks and OutOfMemoryError
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OutOfMemoryError&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Java&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt; &lt;span class="n"&gt;space&lt;/span&gt;
&lt;span class="c1"&gt;// or&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;OutOfMemoryException&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cross-Runtime Reference Leaks
&lt;/h3&gt;

&lt;p&gt;When .NET holds references to Java proxy objects, the Java objects can't be garbage collected. This is the sneaky one:&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;// Problem: Creating Java objects in a loop without cleanup&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;1000000&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="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="nf"&gt;JavaXmlParser&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// Creates JVM object&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="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// parser reference kept alive by .NET GC&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Fix: Dispose Java objects explicitly&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;1000000&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="k"&gt;using&lt;/span&gt; &lt;span class="nn"&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="nf"&gt;JavaXmlParser&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="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Disposed at end of scope, JVM reference released&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dual-Runtime Memory Budgeting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Container with 4GB RAM:
// JVM heap: -Xmx1g (max 1GB)
// CLR heap: System.GC.HeapHardLimit = 1.5GB
// OS + native: 1.5GB
// 
// Common mistake: Setting JVM to 3GB and CLR to 3GB in a 4GB container
// Result: OOM killer terminates the process
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Thread Deadlocks and Timeouts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cross-Runtime Deadlock
&lt;/h3&gt;

&lt;p&gt;Thread A holds a .NET lock and waits for a Java call. Thread B holds a Java lock and waits for a .NET callback. Classic deadlock.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Anti-pattern (deadlock risk):
// .NET calls Java → Java calls back to .NET → .NET calls Java again

// Safe pattern:
// .NET calls Java → Java returns result → .NET processes locally
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Design rule:&lt;/strong&gt; Bridge calls should be one-directional per operation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Thread Pool Starvation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Problem: All .NET thread pool threads blocked on Java bridge calls&lt;/span&gt;
&lt;span class="c1"&gt;// Symptom: ASP.NET Core stops accepting new requests&lt;/span&gt;

&lt;span class="c1"&gt;// BAD:&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&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;javaProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SlowOperation&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;  &lt;span class="c1"&gt;// Consumes thread pool thread&lt;/span&gt;

&lt;span class="c1"&gt;// BETTER: Dedicated thread pool for bridge calls&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;SemaphoreSlim&lt;/span&gt; &lt;span class="n"&gt;_bridgeSemaphore&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;maxCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&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="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CallJava&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;_bridgeSemaphore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitAsync&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;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Factory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartNew&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;javaProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Call&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; 
          &lt;span class="n"&gt;TaskCreationOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LongRunning&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;_bridgeSemaphore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Release&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;h2&gt;
  
  
  SSL and TLS Handshake Failures
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Handshake failure&lt;/td&gt;
&lt;td&gt;TLS version mismatch&lt;/td&gt;
&lt;td&gt;Both sides must support TLS 1.2+. Disable TLS 1.0/1.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Certificate not trusted&lt;/td&gt;
&lt;td&gt;Self-signed or missing CA&lt;/td&gt;
&lt;td&gt;Import cert into Java keystore AND .NET trust store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hostname mismatch&lt;/td&gt;
&lt;td&gt;Cert CN doesn't match&lt;/td&gt;
&lt;td&gt;Use SAN entries matching all hostnames&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cipher suite mismatch&lt;/td&gt;
&lt;td&gt;No common cipher&lt;/td&gt;
&lt;td&gt;Configure matching cipher suites on both runtimes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Import certificate into Java's trust store&lt;/span&gt;
keytool &lt;span class="nt"&gt;-import&lt;/span&gt; &lt;span class="nt"&gt;-trustcacerts&lt;/span&gt; &lt;span class="nt"&gt;-keystore&lt;/span&gt; &lt;span class="nv"&gt;$JAVA_HOME&lt;/span&gt;/lib/security/cacerts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-storepass&lt;/span&gt; changeit &lt;span class="nt"&gt;-alias&lt;/span&gt; myservice &lt;span class="nt"&gt;-file&lt;/span&gt; myservice.crt

&lt;span class="c"&gt;# Verify what TLS versions your JDK supports&lt;/span&gt;
java &lt;span class="nt"&gt;-Djavax&lt;/span&gt;.net.debug&lt;span class="o"&gt;=&lt;/span&gt;ssl:handshake &lt;span class="nt"&gt;-jar&lt;/span&gt; test.jar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  JVM Startup Failures
&lt;/h2&gt;

&lt;p&gt;The "it won't even start" category:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;JAVA_HOME not set or wrong version&lt;/strong&gt; — &lt;code&gt;java -version&lt;/code&gt; and &lt;code&gt;echo $JAVA_HOME&lt;/code&gt; are your friends&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Insufficient memory&lt;/strong&gt; — &lt;code&gt;-Xmx&lt;/code&gt; larger than available RAM → "Could not reserve enough space for object heap"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;32-bit vs 64-bit mismatch&lt;/strong&gt; — A 32-bit .NET process can't load a 64-bit JVM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JVM already initialized&lt;/strong&gt; — JVM can only be created once per process. Use a singleton for bridge init.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Serialization and Marshaling Errors
&lt;/h2&gt;

&lt;h3&gt;
  
  
  JSON Casing Mismatch (REST/gRPC)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Java sends: {"firstName": "John"}  (camelCase)&lt;/span&gt;
&lt;span class="c1"&gt;// .NET expects: {"FirstName": "John"} (PascalCase)&lt;/span&gt;

&lt;span class="c1"&gt;// Fix: Case-insensitive deserialization&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&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;JsonSerializerOptions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PropertyNameCaseInsensitive&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;PropertyNamingPolicy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonNamingPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CamelCase&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Null Handling Across Runtimes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Java: method returns null Integer&lt;/span&gt;
&lt;span class="c1"&gt;// .NET: can't unbox null to int (value type)&lt;/span&gt;

&lt;span class="c1"&gt;// Fix: Use nullable value types&lt;/span&gt;
&lt;span class="kt"&gt;int&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;javaProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// Can be null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Diagnostic Tools Quick Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Java Tool&lt;/th&gt;
&lt;th&gt;.NET Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Thread dump / deadlock&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jstack &amp;lt;pid&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dotnet-dump collect&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap analysis&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jmap -dump:format=b &amp;lt;pid&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dotnet-gcdump collect&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GC behavior&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-Xlog:gc*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dotnet-counters monitor&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU profiling&lt;/td&gt;
&lt;td&gt;JDK Flight Recorder / async-profiler&lt;/td&gt;
&lt;td&gt;dotnet-trace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Class loading&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-verbose:class&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Assembly.Load events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network issues&lt;/td&gt;
&lt;td&gt;&lt;code&gt;-Djavax.net.debug=ssl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DOTNET_SYSTEM_NET_HTTP_SOCKETSHTTPHANDLER_LOG&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I get a cross-runtime stack trace?&lt;/strong&gt;&lt;br&gt;
When a Java exception occurs during a bridge call, it's wrapped in a .NET exception. Check &lt;code&gt;ex.InnerException&lt;/code&gt; for the original Java exception class and stack trace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Works locally, fails in Docker?&lt;/strong&gt;&lt;br&gt;
Check: (1) JAVA_HOME path differs, (2) memory limits are lower in containers, (3) DNS resolution differs in container networking, (4) file permissions on JARs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I debug both runtimes simultaneously?&lt;/strong&gt;&lt;br&gt;
Yes — attach a Java remote debugger (&lt;code&gt;-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005&lt;/code&gt;) and a .NET debugger to the same process. Set breakpoints on both sides.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I know if the error is in Java, .NET, or the bridge?&lt;/strong&gt;&lt;br&gt;
Three tests: (1) Call the Java method directly from Java — if it fails, Java problem. (2) Call a trivial bridge method like &lt;code&gt;toString()&lt;/code&gt; — if that fails, bridge misconfigured. (3) If both work, check parameter types, null handling, and thread safety in the cross-runtime call.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;For more depth, see the &lt;a href="https://jnbridge.com/jnbridgepro/java-dotnet-performance-tuning-reducing-latency" rel="noopener noreferrer"&gt;Performance Tuning Guide&lt;/a&gt; and &lt;a href="https://jnbridge.com/jnbridgepro/java-dotnet-integration-security-authentication-encryption" rel="noopener noreferrer"&gt;Security Guide&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Testing Across the Java/.NET Boundary: A Practical Strategy That Actually Works</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Tue, 17 Mar 2026 13:41:51 +0000</pubDate>
      <link>https://forem.com/jnbridge/testing-across-the-javanet-boundary-a-practical-strategy-that-actually-works-27if</link>
      <guid>https://forem.com/jnbridge/testing-across-the-javanet-boundary-a-practical-strategy-that-actually-works-27if</guid>
      <description>&lt;p&gt;If you've ever stared at a green CI pipeline and then watched your Java/.NET integration explode in production — you know that standard unit tests aren't enough when two runtimes are talking to each other.&lt;/p&gt;

&lt;p&gt;The bridge boundary between Java and .NET is a unique layer that most testing strategies completely ignore. Serialization failures, type mapping bugs, classpath issues — none of these show up in your mocked unit tests. Here's a testing strategy designed specifically for polyglot Java/.NET architectures, with code you can actually use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Testing Pyramid Gets a New Layer
&lt;/h2&gt;

&lt;p&gt;The standard testing pyramid (unit → integration → E2E) works for single-language apps. But Java/.NET integration needs a distinct &lt;strong&gt;bridge test&lt;/strong&gt; layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;         ╱╲
        ╱ E2E ╲          Few: Full system tests
       ╱────────╲
      ╱ Bridge    ╲       Medium: Cross-language integration
     ╱──────────────╲
    ╱ Integration     ╲   Medium: Each side's service tests
   ╱────────────────────╲
  ╱ Unit                  ╲ Many: Pure logic tests per language
 ╱──────────────────────────╲
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unit tests:&lt;/strong&gt; Test Java and .NET logic independently. Mock the bridge boundary. Run in milliseconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration tests (per side):&lt;/strong&gt; Test each side with real dependencies (DBs, file systems) but mock the cross-language boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bridge tests:&lt;/strong&gt; Test that Java and .NET &lt;em&gt;actually communicate correctly&lt;/em&gt; through the bridge. Requires both runtimes running.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;E2E tests:&lt;/strong&gt; Full system tests across both platforms.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Unit Testing: Mock the Bridge, Not the Logic
&lt;/h2&gt;

&lt;p&gt;The key is abstracting the bridge behind an interface so your business logic is testable without a JVM:&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;// ❌ Bad: Tight coupling to Java bridge&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;RiskService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;CalculateRisk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Portfolio&lt;/span&gt; &lt;span class="n"&gt;portfolio&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;javaEngine&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;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RiskEngine&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="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;javaEngine&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;portfolio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToJavaObject&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;// ✅ Good: Interface abstraction&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IRiskEngine&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;CalculateVaR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Portfolio&lt;/span&gt; &lt;span class="n"&gt;portfolio&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;confidence&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;CalculateExpectedShortfall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Portfolio&lt;/span&gt; &lt;span class="n"&gt;portfolio&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Production implementation uses the bridge&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;JnBridgeRiskEngine&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRiskEngine&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;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RiskEngine&lt;/span&gt; &lt;span class="n"&gt;_javaEngine&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;JnBridgeRiskEngine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_javaEngine&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;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;risk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RiskEngine&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="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;CalculateVaR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Portfolio&lt;/span&gt; &lt;span class="n"&gt;portfolio&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;confidence&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;javaPortfolio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PortfolioMapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToJava&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;portfolio&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="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;_javaEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateVaR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;javaPortfolio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;confidence&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="kt"&gt;decimal&lt;/span&gt; &lt;span class="nf"&gt;CalculateExpectedShortfall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Portfolio&lt;/span&gt; &lt;span class="n"&gt;portfolio&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;javaPortfolio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PortfolioMapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToJava&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;portfolio&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="kt"&gt;decimal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;_javaEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;javaPortfolio&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;// Unit test with mock — no JVM needed&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestFixture&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;PortfolioServiceTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&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;RebalancePortfolio_WhenRiskExceedsThreshold_ReducesExposure&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;mockRiskEngine&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;Mock&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IRiskEngine&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;mockRiskEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateVaR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;It&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAny&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Portfolio&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(),&lt;/span&gt; &lt;span class="m"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Returns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1_500_000m&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;service&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;PortfolioService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mockRiskEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Object&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;portfolio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateTestPortfolio&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="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Rebalance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;portfolio&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&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;TotalExposure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;portfolio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalExposure&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;Same pattern on the Java side — if Java calls back into .NET:&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;DotNetNotificationService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;notifyTradeExecuted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;tradeId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;notifyRiskAlert&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;portfolioId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;varValue&lt;/span&gt;&lt;span class="o"&gt;);&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;TradeExecutionService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;DotNetNotificationService&lt;/span&gt; &lt;span class="n"&gt;notificationService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;TradeExecutionService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;DotNetNotificationService&lt;/span&gt; &lt;span class="n"&gt;notificationService&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notificationService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;notificationService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;TradeResult&lt;/span&gt; &lt;span class="nf"&gt;executeTrade&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TradeOrder&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matchingEngine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;submit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;notificationService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notifyTradeExecuted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTradeId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPrice&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getQuantity&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&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;span class="nd"&gt;@ExtendWith&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;MockitoExtension&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TradeExecutionServiceTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Mock&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;DotNetNotificationService&lt;/span&gt; &lt;span class="n"&gt;notificationService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@InjectMocks&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;TradeExecutionService&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;executeTrade_notifiesDotNetOnSuccess&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;order&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;TradeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AAPL"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Side&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;BUY&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="mf"&gt;185.50&lt;/span&gt;&lt;span class="o"&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;executeTrade&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;notificationService&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;notifyTradeExecuted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getTradeId&lt;/span&gt;&lt;span class="o"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;185.50&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="n"&gt;eq&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="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bridge Integration Tests: The Layer Most People Skip
&lt;/h2&gt;

&lt;p&gt;This is where you catch the real bugs — type mapping failures, serialization issues, classpath problems:&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestFixture&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BridgeIntegration"&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;RiskEngineIntegrationTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;JnBridgeRiskEngine&lt;/span&gt; &lt;span class="n"&gt;_riskEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;OneTimeSetUp&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;SetUp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;BridgeConfiguration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Initialize&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;BridgeConfig&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="n"&gt;Environment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEnvironmentVariable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"JAVA_HOME"&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="s"&gt;"java-libs/risk-engine.jar;java-libs/dependencies/*"&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="n"&gt;_riskEngine&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;JnBridgeRiskEngine&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="n"&gt;Test&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;CalculateVaR_WithValidPortfolio_ReturnsPositiveValue&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;portfolio&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;Portfolio&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;portfolio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AAPL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;185.50m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;portfolio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MSFT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;420.30m&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="n"&gt;_riskEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateVaR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;portfolio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&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;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GreaterThan&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;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&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;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;portfolio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalValue&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="n"&gt;Test&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;CalculateVaR_WithHighConfidence_ReturnsHigherValue&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;portfolio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CreateLargePortfolio&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;var95&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_riskEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateVaR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;portfolio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.95&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;var99&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_riskEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateVaR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;portfolio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;var95&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="n"&gt;OneTimeTearDown&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;TearDown&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;BridgeConfiguration&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Type Mapping Tests — Where the Real Bugs Hide
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestFixture&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"BridgeIntegration"&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;TypeMappingTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&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;JavaBigDecimal_MapsTo_DotNetDecimal&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;javaCalc&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;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Calculator&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="n"&gt;javaCalc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PreciseCalculation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&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;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3.3m&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0.0001m&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="n"&gt;Test&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;JavaLocalDateTime_MapsTo_DotNetDateTime&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;javaTimeService&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;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TimeService&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;javaTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javaTimeService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCurrentTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;javaTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EqualTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Within&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;5&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="n"&gt;Test&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;JavaException_PropagatesTo_DotNetException&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;javaService&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;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DataService&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;ex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Throws&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;javaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MethodThatThrows&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Does&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected error message"&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="nf"&gt;TestCase&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="nf"&gt;TestCase&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="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"very long string with unicode: こんにちは 🎉"&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;JavaString_HandlesEdgeCases&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;input&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;javaService&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;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StringService&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="n"&gt;javaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Echo&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;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&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;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EqualTo&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Tests: Don't Assume, Measure
&lt;/h2&gt;

&lt;p&gt;These catch regressions when Java libs update, bridge versions change, or JVM settings are modified:&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TestFixture&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Performance"&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;BridgePerformanceTests&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Test&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;InProcessBridge_SimpleCall_Under100Microseconds&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;javaService&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;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EchoService&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Warmup&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;javaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"warmup"&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;sw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartNew&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;iterations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10_000&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="n"&gt;iterations&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;javaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&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;avgMicroseconds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elapsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalMicroseconds&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avgMicroseconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
            &lt;span class="s"&gt;"Bridge call latency exceeded 100μs threshold"&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="n"&gt;Test&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;Bridge_Under10KConcurrentCalls_NoErrors&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;javaService&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;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ThreadSafeService&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="n"&gt;ConcurrentBag&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="n"&gt;Parallel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;For&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="m"&gt;10_000&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;ParallelOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;MaxDegreeOfParallelism&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;50&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;gt;&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="n"&gt;javaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"item-&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="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;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&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="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&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="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&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="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="s"&gt;$"Bridge had &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="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; errors under concurrent load"&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="n"&gt;Test&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;Bridge_MemoryStable_Over1MillionCalls&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;javaService&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;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DataService&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;initialMemory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTotalMemory&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;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;1_000_000&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;javaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSmallObject&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="n"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Collect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitForPendingFinalizers&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;finalMemory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetTotalMemory&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;memoryGrowthMB&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finalMemory&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;initialMemory&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="m"&gt;1024.0&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1024.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;That&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memoryGrowthMB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
            &lt;span class="s"&gt;"Possible memory leak — growth exceeded 50MB over 1M calls"&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;h2&gt;
  
  
  Docker-Based Test Environment
&lt;/h2&gt;

&lt;p&gt;Bridge integration tests need both runtimes. Docker makes it reproducible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile.test — Both runtimes in one container&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/sdk:9.0&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    openjdk-21-jdk-headless &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/lib/apt/lists/&lt;span class="k"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /test&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; dotnet build &lt;span class="nt"&gt;-c&lt;/span&gt; Release

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "test", "-c", "Release", "--logger", "trx"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.test.yml&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;unit-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;.&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Dockerfile.test&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dotnet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Release"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; 
              &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--filter"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Category!=BridgeIntegration&amp;amp;Category!=Performance"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;test-results&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;/test/TestResults&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;bridge-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;.&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Dockerfile.test&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dotnet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Release"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
              &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--filter"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Category=BridgeIntegration"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;test-results&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;/test/TestResults&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;unit-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;service_completed_successfully&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

  &lt;span class="na"&gt;performance-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;.&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;Dockerfile.test&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dotnet"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Release"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
              &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--filter"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Category=Performance"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;test-results&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;/test/TestResults&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;bridge-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;service_completed_successfully&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test-results&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CI/CD Pipeline: GitHub Actions
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Java/.NET Integration Tests&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;develop&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;unit-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;dotnet-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;9.0.x'&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet test --filter "Category!=BridgeIntegration&amp;amp;Category!=Performance"&lt;/span&gt;

  &lt;span class="na"&gt;java-unit-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;21'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temurin'&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./gradlew test&lt;/span&gt;

  &lt;span class="na"&gt;bridge-tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;unit-tests&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;java-unit-tests&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;dotnet-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;9.0.x'&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;21'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temurin'&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet test --filter "Category=BridgeIntegration"&lt;/span&gt;
        &lt;span class="na"&gt;timeout-minutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

  &lt;span class="na"&gt;performance-gate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;bridge-tests&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref == 'refs/heads/main'&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-dotnet@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;dotnet-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;9.0.x'&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;21'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temurin'&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dotnet test --filter "Category=Performance"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pipeline strategy:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Tests&lt;/th&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PR checks&lt;/td&gt;
&lt;td&gt;Unit tests (both languages)&lt;/td&gt;
&lt;td&gt;Every PR&lt;/td&gt;
&lt;td&gt;~2 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PR checks&lt;/td&gt;
&lt;td&gt;Bridge integration tests&lt;/td&gt;
&lt;td&gt;Every PR&lt;/td&gt;
&lt;td&gt;~5 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merge to main&lt;/td&gt;
&lt;td&gt;Performance tests&lt;/td&gt;
&lt;td&gt;After merge&lt;/td&gt;
&lt;td&gt;~10 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nightly&lt;/td&gt;
&lt;td&gt;Full E2E + load tests&lt;/td&gt;
&lt;td&gt;Scheduled&lt;/td&gt;
&lt;td&gt;~30 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pre-release&lt;/td&gt;
&lt;td&gt;All tests + security scan&lt;/td&gt;
&lt;td&gt;Before deploy&lt;/td&gt;
&lt;td&gt;~45 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  6 Pitfalls That Will Bite You
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Not testing type boundaries.&lt;/strong&gt; Java BigDecimal → .NET decimal, LocalDateTime → DateTime, ArrayList → List — test every type that crosses the bridge. These are your #1 production failure source.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ignoring thread safety.&lt;/strong&gt; If your bridge calls are concurrent in production (they usually are), test with concurrent load. Some bridge configs aren't thread-safe by default.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Skipping classpath smoke tests.&lt;/strong&gt; A missing JAR causes cryptic failures. Write a test that verifies all required Java classes are loadable at bridge init.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing only the happy path.&lt;/strong&gt; What happens when Java throws? Does the exception propagate correctly to .NET? Test timeouts, OOM, and failure modes explicitly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No performance baselines.&lt;/strong&gt; Without baselines, you won't notice a 10x latency regression from a Java library update. Assert thresholds in your perf tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Running bridge tests in parallel.&lt;/strong&gt; Bridge initialization often isn't parallelizable. Use &lt;code&gt;[NonParallelizable]&lt;/code&gt; in NUnit or sequential execution for bridge setup/teardown.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Testing Checklist
&lt;/h2&gt;

&lt;p&gt;Use this when setting up testing for a Java/.NET integration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;☐ Business logic unit tests (both sides) with mocked bridge boundary&lt;/li&gt;
&lt;li&gt;☐ Type mapping tests for every type crossing the bridge&lt;/li&gt;
&lt;li&gt;☐ Null handling tests for all bridge method parameters&lt;/li&gt;
&lt;li&gt;☐ Exception propagation tests (Java → .NET)&lt;/li&gt;
&lt;li&gt;☐ Collection mapping tests (ArrayList → List, HashMap → Dictionary)&lt;/li&gt;
&lt;li&gt;☐ Unicode/encoding tests for string parameters&lt;/li&gt;
&lt;li&gt;☐ Date/time/timezone tests&lt;/li&gt;
&lt;li&gt;☐ Concurrent load test (50+ parallel calls)&lt;/li&gt;
&lt;li&gt;☐ Memory stability test (1M+ calls)&lt;/li&gt;
&lt;li&gt;☐ Latency baseline with asserted threshold&lt;/li&gt;
&lt;li&gt;☐ Bridge init smoke test (all JARs loadable)&lt;/li&gt;
&lt;li&gt;☐ Docker-based test environment for CI&lt;/li&gt;
&lt;li&gt;☐ CI pipeline with staged execution&lt;/li&gt;
&lt;li&gt;☐ Performance regression gate on main branch&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Testing Java/.NET integration requires a deliberate strategy beyond standard unit testing. The bridge boundary introduces type mapping, serialization, concurrency, and performance concerns that don't exist in single-language apps.&lt;/p&gt;

&lt;p&gt;The approach here — interface abstraction for unit tests, dedicated bridge tests, Docker environments, and CI/CD with performance gates — gives you confidence your integration works correctly even as both sides evolve.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://jnbridge.com" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt; supports both in-process and TCP testing modes, so you can run bridge tests locally during development and in Docker containers during CI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building a tested Java/.NET integration?&lt;/strong&gt; &lt;a href="https://jnbridge.com/jnbridgepro" rel="noopener noreferrer"&gt;Download JNBridgePro&lt;/a&gt; and try these patterns with your own codebase.&lt;/p&gt;

</description>
      <category>testing</category>
      <category>java</category>
      <category>dotnet</category>
      <category>devops</category>
    </item>
    <item>
      <title>Spring Boot + ASP.NET Core in the Same Architecture? Here Are 4 Patterns That Work</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Fri, 13 Mar 2026 13:10:47 +0000</pubDate>
      <link>https://forem.com/jnbridge/spring-boot-aspnet-core-in-the-same-architecture-here-are-4-patterns-that-work-5cga</link>
      <guid>https://forem.com/jnbridge/spring-boot-aspnet-core-in-the-same-architecture-here-are-4-patterns-that-work-5cga</guid>
      <description>&lt;p&gt;Nobody sets out to run both Spring Boot and ASP.NET Core. But acquisitions happen, teams make different (valid) technology choices, and suddenly you're staring at two frameworks that need to talk to each other.&lt;/p&gt;

&lt;p&gt;I've been working on Java/.NET integration for years, and these are the four patterns I keep coming back to — each with real code you can adapt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why These Frameworks End Up Together
&lt;/h2&gt;

&lt;p&gt;Teams don't usually plan to run both frameworks. It happens because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Acquisitions:&lt;/strong&gt; Company A (.NET shop) acquires Company B (Java shop). Now you have Spring Boot microservices talking to ASP.NET Core APIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Best-of-breed choices:&lt;/strong&gt; The data engineering team chose Spring Boot for Kafka integration. The frontend team chose ASP.NET Core for the API gateway. Both are valid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Legacy + Modern:&lt;/strong&gt; A mature Spring Boot backend serves a new ASP.NET Core frontend built for a modern SPA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vendor libraries:&lt;/strong&gt; A critical third-party SDK only ships as a Java JAR. Your application is ASP.NET Core.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pattern 1: REST API Integration
&lt;/h2&gt;

&lt;p&gt;The most common pattern. Spring Boot exposes REST endpoints, ASP.NET Core consumes them (or vice versa).&lt;/p&gt;

&lt;h3&gt;
  
  
  Spring Boot Side (API Provider)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="nd"&gt;@RequestMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/products"&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;ProductController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ProductService&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/{id}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductDTO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;ResponseEntity:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notFound&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Page&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductDTO&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;searchProducts&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nd"&gt;@RequestParam&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;Pageable&lt;/span&gt; &lt;span class="n"&gt;pageable&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pageable&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;h3&gt;
  
  
  ASP.NET Core Side (API Consumer)
&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;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProductApiClient&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;HttpClient&lt;/span&gt; &lt;span class="n"&gt;_http&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;ProductApiClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpClient&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&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="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;id&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;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"/api/products/&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;id&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;if&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;IsSuccessStatusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&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;Product&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;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="n"&gt;PagedResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SearchAsync&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;query&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;page&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="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&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;_http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PagedResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="s"&gt;$"/api/products?query=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;page=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;size=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;size&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;response&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;// Registration in Program.cs&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ProductApiClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&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="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;"http://spring-boot-service:8080"&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="n"&gt;Timeout&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;10&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="nf"&gt;AddTransientHttpErrorPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WaitAndRetryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="p"&gt;=&amp;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;FromMilliseconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTransientHttpErrorPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; 
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CircuitBreakerAsync&lt;/span&gt;&lt;span class="p"&gt;(&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Spring Boot's &lt;code&gt;Page&amp;lt;T&amp;gt;&lt;/code&gt; pagination uses a different JSON structure than what ASP.NET Core expects. Map accordingly.&lt;/li&gt;
&lt;li&gt;Spring Boot returns dates as ISO-8601 by default (Jackson). &lt;code&gt;System.Text.Json&lt;/code&gt; handles this, but verify timezone handling.&lt;/li&gt;
&lt;li&gt;Add Polly for retry and circuit breaker on the .NET side.&lt;/li&gt;
&lt;li&gt;Spring Boot's &lt;code&gt;@Valid&lt;/code&gt; errors return 400 with a different shape than ASP.NET Core's &lt;code&gt;ProblemDetails&lt;/code&gt;. Normalize your error handling.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pattern 2: gRPC Integration
&lt;/h2&gt;

&lt;p&gt;For higher-performance scenarios, gRPC gives you better throughput with strong typing via Protobuf:&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;product&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;ProductService&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;GetProduct&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ProductRequest&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;ProductResponse&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;StreamPriceUpdates&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PriceSubscription&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;PriceUpdate&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;ProductRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int64&lt;/span&gt; &lt;span class="na"&gt;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;ProductResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int64&lt;/span&gt; &lt;span class="na"&gt;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="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;name&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="kt"&gt;string&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;google.protobuf.Timestamp&lt;/span&gt; &lt;span class="na"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Spring Boot gRPC server&lt;/span&gt;
&lt;span class="nd"&gt;@GrpcService&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;ProductGrpcService&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;ProductServiceGrpc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ProductServiceImplBase&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;getProduct&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProductRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
                          &lt;span class="nc"&gt;StreamObserver&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProductResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;productService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onNext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toProto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onCompleted&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;streamPriceUpdates&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;PriceSubscription&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
                                   &lt;span class="nc"&gt;StreamObserver&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PriceUpdate&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;priceService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;subscribe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSymbol&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;observer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onNext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;toPriceProto&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&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;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ASP.NET Core gRPC client&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;ProductGrpcClient&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;ProductService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProductServiceClient&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="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="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetProductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;id&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;GetProductAsync&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;ProductRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&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;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromProto&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="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;IAsyncEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PriceUpdate&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;StreamPricesAsync&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;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;EnumeratorCancellation&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&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;stream&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="nf"&gt;StreamPriceUpdates&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;PriceSubscription&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Symbol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&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;update&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseStream&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAllAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;PriceUpdate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromProto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&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;gRPC's server-streaming is particularly useful when Spring Boot pushes real-time data to ASP.NET Core consumers — prices, events, notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: In-Process Bridge
&lt;/h2&gt;

&lt;p&gt;When ASP.NET Core needs to use Java libraries directly — without running a separate Spring Boot service — an in-process bridge loads the JVM inside the .NET process:&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;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JavaRuleEngineService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IRuleEngine&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;com&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RuleEngine&lt;/span&gt; &lt;span class="n"&gt;_javaEngine&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;JavaRuleEngineService&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Bridge loads the Java rule engine in-process&lt;/span&gt;
        &lt;span class="c1"&gt;// No separate Spring Boot service needed&lt;/span&gt;
        &lt;span class="n"&gt;_javaEngine&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;company&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RuleEngine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_javaEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadRules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/rules/production.drl"&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="n"&gt;RuleResult&lt;/span&gt; &lt;span class="nf"&gt;Evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BusinessContext&lt;/span&gt; &lt;span class="n"&gt;context&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;javaContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ContextMapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToJava&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&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="n"&gt;_javaEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;javaContext&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;RuleResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromJava&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Register in ASP.NET Core DI&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IRuleEngine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;JavaRuleEngineService&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;&lt;strong&gt;When to use this:&lt;/strong&gt; When you need a Java library (Drools, Apache Tika, a proprietary Java SDK) but don't want to deploy and maintain a separate Spring Boot service just to expose it. Tools like &lt;a href="https://jnbridge.com" rel="noopener noreferrer"&gt;JNBridgePro&lt;/a&gt; make this possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: Event-Driven Integration via Kafka
&lt;/h2&gt;

&lt;p&gt;For async workflows, both frameworks communicate through Apache Kafka (or RabbitMQ, Azure Service Bus):&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;// Spring Boot producer&lt;/span&gt;
&lt;span class="nd"&gt;@Service&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;OrderEventPublisher&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Autowired&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;KafkaTemplate&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;kafka&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;publishOrderCreated&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;kafka&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;send&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-events"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&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;OrderCreatedEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ASP.NET Core consumer&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;OrderEventConsumer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&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;consumer&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;ConsumerBuilder&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;,&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="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-events"&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;ct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&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;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Consume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&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;orderEvent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;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;Message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ProcessOrderEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderEvent&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;&lt;strong&gt;Schema compatibility tip:&lt;/strong&gt; Use Confluent Schema Registry with Avro or Protobuf schemas. Both Spring Boot (spring-kafka) and .NET (Confluent.SchemaRegistry) support it, ensuring both sides agree on message formats.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shared Cross-Cutting Concerns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Authentication: Shared JWT Tokens
&lt;/h3&gt;

&lt;p&gt;Both frameworks can validate the same JWT tokens from the same identity provider:&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;// Spring Boot&lt;/span&gt;
&lt;span class="nd"&gt;@Configuration&lt;/span&gt;
&lt;span class="nd"&gt;@EnableWebSecurity&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;SecurityConfig&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;SecurityFilterChain&lt;/span&gt; &lt;span class="nf"&gt;filterChain&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpSecurity&lt;/span&gt; &lt;span class="n"&gt;http&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;oauth2ResourceServer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oauth2&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;oauth2&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jwt&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;jwt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;jwkSetUri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://auth.company.com/.well-known/jwks"&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ASP.NET Core&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JwtBearerDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuthenticationScheme&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddJwtBearer&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;=&amp;gt;&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="n"&gt;Authority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://auth.company.com"&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="n"&gt;TokenValidationParameters&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;TokenValidationParameters&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ValidateIssuer&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;ValidIssuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://auth.company.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ValidateAudience&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;ValidAudience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"api"&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;
  
  
  Distributed Tracing with OpenTelemetry
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Spring Boot: application.yml&lt;/span&gt;
&lt;span class="na"&gt;otel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;spring-product-service&lt;/span&gt;
  &lt;span class="na"&gt;exporter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://jaeger:4317&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ASP.NET Core&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTracing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tracing&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tracing&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetResourceBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ResourceBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateDefault&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dotnet-api-gateway"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAspNetCoreInstrumentation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClientInstrumentation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddGrpcClientInstrumentation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOtlpExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoint&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;"http://jaeger:4317"&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With both exporting to the same collector, you get unified traces showing requests flowing across framework boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Pattern
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Separate teams, separate deployments&lt;/td&gt;
&lt;td&gt;REST or gRPC&lt;/td&gt;
&lt;td&gt;Clear contracts, independent scaling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-time data streaming&lt;/td&gt;
&lt;td&gt;gRPC server-streaming or Kafka&lt;/td&gt;
&lt;td&gt;Efficient push model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need Java library in .NET app&lt;/td&gt;
&lt;td&gt;In-process bridge&lt;/td&gt;
&lt;td&gt;No separate service to deploy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Async event-driven workflow&lt;/td&gt;
&lt;td&gt;Kafka/RabbitMQ&lt;/td&gt;
&lt;td&gt;Decoupled, resilient, scalable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;High-frequency calls (&amp;gt;1K/sec)&lt;/td&gt;
&lt;td&gt;gRPC or in-process bridge&lt;/td&gt;
&lt;td&gt;REST too slow at volume&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migration in progress&lt;/td&gt;
&lt;td&gt;In-process bridge&lt;/td&gt;
&lt;td&gt;Temporary integration without throwaway APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Framework Feature Mapping
&lt;/h2&gt;

&lt;p&gt;For developers working across both, here's how the core concepts map:&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;Spring Boot&lt;/th&gt;
&lt;th&gt;ASP.NET Core&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DI Container&lt;/td&gt;
&lt;td&gt;Spring IoC / @Autowired&lt;/td&gt;
&lt;td&gt;builder.Services / [FromServices]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Middleware&lt;/td&gt;
&lt;td&gt;Filters / Interceptors&lt;/td&gt;
&lt;td&gt;app.Use() pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;application.yml / @Value&lt;/td&gt;
&lt;td&gt;appsettings.json / IConfiguration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health Checks&lt;/td&gt;
&lt;td&gt;Actuator /health&lt;/td&gt;
&lt;td&gt;MapHealthChecks()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metrics&lt;/td&gt;
&lt;td&gt;Micrometer&lt;/td&gt;
&lt;td&gt;System.Diagnostics.Metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ORM&lt;/td&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;Validation&lt;/td&gt;
&lt;td&gt;Bean Validation / &lt;a class="mentioned-user" href="https://dev.to/valid"&gt;@valid&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;DataAnnotations / FluentValidation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background Jobs&lt;/td&gt;
&lt;td&gt;@Scheduled / Spring Batch&lt;/td&gt;
&lt;td&gt;BackgroundService / Hangfire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API Docs&lt;/td&gt;
&lt;td&gt;SpringDoc / Swagger&lt;/td&gt;
&lt;td&gt;Swashbuckle / NSwag&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Spring Boot and ASP.NET Core are more alike than different. When they need to cooperate, the right pattern depends on your coupling requirements, performance needs, and team structure. REST handles most cases. gRPC wins on performance-sensitive paths. Kafka decouples event-driven flows. And an in-process bridge gives you direct library access without the operational overhead of another service.&lt;/p&gt;

&lt;p&gt;The real power move? Use multiple patterns in the same system, each where it fits best.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://jnbridge.com/jnbridgepro/spring-boot-aspnet-core-integration-patterns" rel="noopener noreferrer"&gt;jnbridge.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>dotnet</category>
      <category>springboot</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Squeezing Microseconds: A Practical Guide to Java/.NET Performance Tuning</title>
      <dc:creator>JNBridge</dc:creator>
      <pubDate>Tue, 10 Mar 2026 13:23:09 +0000</pubDate>
      <link>https://forem.com/jnbridge/squeezing-microseconds-a-practical-guide-to-javanet-performance-tuning-pp4</link>
      <guid>https://forem.com/jnbridge/squeezing-microseconds-a-practical-guide-to-javanet-performance-tuning-pp4</guid>
      <description>&lt;p&gt;If you're calling Java from .NET (or vice versa), you've probably noticed that cross-runtime calls aren't free. The bridge overhead itself is tiny — microseconds per call — but when you're making thousands of calls per request, those microseconds stack up fast.&lt;/p&gt;

&lt;p&gt;I've spent a lot of time profiling cross-runtime performance in production systems, and the surprising truth is: &lt;strong&gt;the bridge is almost never the bottleneck.&lt;/strong&gt; GC pauses, chatty call patterns, and object marshaling eat way more time. Here's everything I've learned about making Java/.NET integration fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Latency Actually Hides
&lt;/h2&gt;

&lt;p&gt;Most teams blame the bridge. In practice, here's the real breakdown:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Typical Latency&lt;/th&gt;
&lt;th&gt;How to Detect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bridge call overhead&lt;/td&gt;
&lt;td&gt;1–50µs&lt;/td&gt;
&lt;td&gt;Microbenchmark isolated calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object marshaling/serialization&lt;/td&gt;
&lt;td&gt;10–500µs&lt;/td&gt;
&lt;td&gt;Profile with complex objects vs primitives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GC pauses (either runtime)&lt;/td&gt;
&lt;td&gt;1–200ms&lt;/td&gt;
&lt;td&gt;GC logs (both JVM and CLR)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JVM cold start (first call)&lt;/td&gt;
&lt;td&gt;1–5s&lt;/td&gt;
&lt;td&gt;Measure first call vs subsequent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Class loading (Java)&lt;/td&gt;
&lt;td&gt;10–100ms&lt;/td&gt;
&lt;td&gt;Profile with &lt;code&gt;-verbose:class&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JIT compilation (both runtimes)&lt;/td&gt;
&lt;td&gt;50–500ms first execution&lt;/td&gt;
&lt;td&gt;Warmup timing, tiered compilation logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thread contention at bridge&lt;/td&gt;
&lt;td&gt;Variable&lt;/td&gt;
&lt;td&gt;Thread dump analysis, lock profiling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network latency (TCP mode)&lt;/td&gt;
&lt;td&gt;0.1–1ms per call&lt;/td&gt;
&lt;td&gt;Switch to shared memory, compare&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; If your cross-runtime calls are slower than expected, look at GC, class loading, and call patterns first — not the bridge mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure Before You Optimize
&lt;/h2&gt;

&lt;p&gt;Performance tuning without measurement is guessing. Establish baselines first:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Single call latency&lt;/strong&gt; — One method call with a primitive parameter. This is your overhead floor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex call latency&lt;/strong&gt; — Same call with realistic objects (lists, custom classes). Difference = marshaling cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throughput&lt;/strong&gt; — Max calls/sec before latency degrades. Tests concurrency limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P99 latency&lt;/strong&gt; — The 99th percentile matters more than average. GC pauses cause tail spikes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold start time&lt;/strong&gt; — First call after JVM init. Worst-case latency.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Benchmarking Template (C#)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BenchmarkDotNet setup for cross-runtime calls&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MemoryDiagnoser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;GcServer&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;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BridgeCallBenchmarks&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;JavaProxy&lt;/span&gt; &lt;span class="n"&gt;_proxy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;GlobalSetup&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;Setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_proxy&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;JavaProxy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Warmup: 1000 calls to trigger JIT on both sides&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;_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SimpleCall&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="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Baseline&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;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;SimpleCall&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;_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;58&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Benchmark&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;List&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;ComplexCall&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;_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Benchmark&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;TradeResult&lt;/span&gt; &lt;span class="nf"&gt;RealWorldCall&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;_proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteTrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sampleTrade&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;h2&gt;
  
  
  JVM Tuning for Bridge Workloads
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Heap Sizing
&lt;/h3&gt;

&lt;p&gt;When the JVM runs inside (or alongside) a .NET process, memory is shared. Set explicit bounds:&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;# Recommended JVM flags for bridge workloads&lt;/span&gt;
&lt;span class="nt"&gt;-Xms512m&lt;/span&gt;                        &lt;span class="c"&gt;# Initial heap (avoid resize delays)&lt;/span&gt;
&lt;span class="nt"&gt;-Xmx1g&lt;/span&gt;                          &lt;span class="c"&gt;# Maximum heap (leave room for CLR)&lt;/span&gt;
&lt;span class="nt"&gt;-XX&lt;/span&gt;:MaxMetaspaceSize&lt;span class="o"&gt;=&lt;/span&gt;256m       &lt;span class="c"&gt;# Cap class metadata&lt;/span&gt;
&lt;span class="nt"&gt;-XX&lt;/span&gt;:ReservedCodeCacheSize&lt;span class="o"&gt;=&lt;/span&gt;128m  &lt;span class="c"&gt;# JIT compiled code cache&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical rule:&lt;/strong&gt; Total JVM heap + CLR managed heap + native overhead must fit in available RAM. In a 4GB container: budget ~1GB for JVM, ~1.5GB for CLR, ~1.5GB for OS and native allocations.&lt;/p&gt;

&lt;h3&gt;
  
  
  GC Selection
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;GC Algorithm&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Bridge Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;G1GC (Java 9+ default)&lt;/td&gt;
&lt;td&gt;General workloads, 1–16GB heap&lt;/td&gt;
&lt;td&gt;Good default. 10–50ms pause target.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZGC&lt;/td&gt;
&lt;td&gt;Ultra-low latency, large heaps&lt;/td&gt;
&lt;td&gt;Sub-millisecond pauses. Best for latency-sensitive bridges.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shenandoah&lt;/td&gt;
&lt;td&gt;Low latency, Red Hat/OpenJDK&lt;/td&gt;
&lt;td&gt;Similar to ZGC. Available in OpenJDK builds.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serial GC&lt;/td&gt;
&lt;td&gt;Small heaps (&amp;lt;256MB)&lt;/td&gt;
&lt;td&gt;Stop-the-world but fast for tiny heaps.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# For low-latency bridge workloads (Java 17+)&lt;/span&gt;
&lt;span class="nt"&gt;-XX&lt;/span&gt;:+UseZGC
&lt;span class="nt"&gt;-XX&lt;/span&gt;:SoftMaxHeapSize&lt;span class="o"&gt;=&lt;/span&gt;768m    &lt;span class="c"&gt;# ZGC returns memory below this&lt;/span&gt;
&lt;span class="nt"&gt;-XX&lt;/span&gt;:ZCollectionInterval&lt;span class="o"&gt;=&lt;/span&gt;5   &lt;span class="c"&gt;# Proactive GC every 5 seconds&lt;/span&gt;

&lt;span class="c"&gt;# For general bridge workloads&lt;/span&gt;
&lt;span class="nt"&gt;-XX&lt;/span&gt;:+UseG1GC
&lt;span class="nt"&gt;-XX&lt;/span&gt;:MaxGCPauseMillis&lt;span class="o"&gt;=&lt;/span&gt;20     &lt;span class="c"&gt;# Target 20ms max pause&lt;/span&gt;
&lt;span class="nt"&gt;-XX&lt;/span&gt;:G1HeapRegionSize&lt;span class="o"&gt;=&lt;/span&gt;4m     &lt;span class="c"&gt;# Optimize for your object sizes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JIT Compiler Optimization
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Enable tiered compilation (default in Java 9+)&lt;/span&gt;
&lt;span class="nt"&gt;-XX&lt;/span&gt;:+TieredCompilation
&lt;span class="c"&gt;# Pre-compile frequently-called bridge methods&lt;/span&gt;
&lt;span class="nt"&gt;-XX&lt;/span&gt;:CompileThreshold&lt;span class="o"&gt;=&lt;/span&gt;100    &lt;span class="c"&gt;# Compile after 100 invocations (default: 10000)&lt;/span&gt;
&lt;span class="c"&gt;# For faster warmup at cost of peak performance:&lt;/span&gt;
&lt;span class="nt"&gt;-XX&lt;/span&gt;:TieredStopAtLevel&lt;span class="o"&gt;=&lt;/span&gt;1     &lt;span class="c"&gt;# Skip C2 compiler (faster startup)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CLR and .NET Runtime Tuning
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Server GC vs Workstation GC
&lt;/h3&gt;

&lt;p&gt;For bridge workloads, always use Server GC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"runtimeOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"configProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"System.GC.Server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"System.GC.Concurrent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"System.GC.HeapHardLimit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1610612736&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; Workstation GC runs on a single thread and blocks longer. Server GC uses one thread per core, with shorter pauses. For concurrent bridge calls, Server GC reduces tail latency significantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  .NET 9 DATAS GC
&lt;/h3&gt;

&lt;p&gt;.NET 9's Dynamic Adaptation to Application Sizes (DATAS) auto-adjusts heap size based on workload — meaning the CLR won't over-allocate when the JVM also needs heap space:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"configProperties"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"System.GC.DynamicAdaptationMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Thread Pool Tuning
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Set minimum threads to avoid pool starvation during bridge calls&lt;/span&gt;
&lt;span class="n"&gt;ThreadPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetMinThreads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;workerThreads&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="n"&gt;ProcessorCount&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="n"&gt;completionPortThreads&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="n"&gt;ProcessorCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Garbage Collection Coordination
&lt;/h2&gt;

&lt;p&gt;The biggest performance killer: &lt;strong&gt;GC pauses in one runtime stalling the other.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the JVM is in a stop-the-world GC pause, .NET threads waiting for bridge responses are blocked. If the CLR triggers its own GC simultaneously — compounding pause.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use low-pause GCs on both sides&lt;/strong&gt; — ZGC (Java) + Server GC (.NET) keeps pauses under 1ms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stagger GC timing&lt;/strong&gt; — Proactive JVM GC during idle periods (&lt;code&gt;-XX:ZCollectionInterval=5&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor both GC logs simultaneously&lt;/strong&gt; — Correlate JVM GC events with .NET events to find compounding pauses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduce object allocation at the bridge boundary&lt;/strong&gt; — Reuse objects, use value types, avoid unnecessary boxing&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Enabling GC Logs for Both Runtimes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# JVM GC logging&lt;/span&gt;
&lt;span class="nt"&gt;-Xlog&lt;/span&gt;:gc&lt;span class="k"&gt;*&lt;/span&gt;:file&lt;span class="o"&gt;=&lt;/span&gt;jvm-gc.log:time,uptime,level,tags:filecount&lt;span class="o"&gt;=&lt;/span&gt;5,filesize&lt;span class="o"&gt;=&lt;/span&gt;10m

&lt;span class="c"&gt;# .NET GC logging&lt;/span&gt;
&lt;span class="nv"&gt;DOTNET_GCLog&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;gc-dotnet.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Optimizing Cross-Runtime Call Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Anti-Pattern: Chatty Calls
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BAD: 1000 individual bridge calls&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="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Count&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="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;javaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ValidateOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&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="c1"&gt;// ~10µs each&lt;/span&gt;
    &lt;span class="c1"&gt;// 1000 * 10µs = 10ms overhead&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern: Batch Calls
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// GOOD: 1 bridge call with batch data&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;javaService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ValidateOrders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// ~50µs total&lt;/span&gt;
&lt;span class="c1"&gt;// 50µs vs 10ms = 200x faster&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; Every cross-runtime call has fixed overhead. Minimize the &lt;em&gt;number&lt;/em&gt; of calls, not the data per call. One call with 1000 items beats 1000 calls with 1 item.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern: Coarse-Grained Interfaces
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// BAD: Fine-grained Java API from .NET&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javaProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&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;address&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javaProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddressId&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;orders&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javaProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetOrders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&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;total&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javaProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CalculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 4 bridge calls&lt;/span&gt;

&lt;span class="c1"&gt;// GOOD: Coarse-grained facade&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javaProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetCustomerSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 1 bridge call — Java handles the joins internally&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Design principle:&lt;/strong&gt; Create coarse-grained Java facades that batch operations per bridge call. Let Java-to-Java calls happen inside the JVM (zero overhead), and only cross the bridge for the final result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern: Async Fire-and-Forget
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// For non-blocking operations (logging, analytics, cache warming)&lt;/span&gt;
&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&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;javaProxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogAnalyticsEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eventData&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Don't await — .NET continues immediately&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Object Marshaling Optimization
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data Type&lt;/th&gt;
&lt;th&gt;Marshaling Cost&lt;/th&gt;
&lt;th&gt;Optimization&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Primitives (int, double, bool)&lt;/td&gt;
&lt;td&gt;Negligible&lt;/td&gt;
&lt;td&gt;Use directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strings&lt;/td&gt;
&lt;td&gt;Low (UTF-16 both sides)&lt;/td&gt;
&lt;td&gt;Avoid unnecessary conversions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arrays of primitives&lt;/td&gt;
&lt;td&gt;Low (bulk copy)&lt;/td&gt;
&lt;td&gt;Prefer over &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simple objects (few fields)&lt;/td&gt;
&lt;td&gt;Low-Medium&lt;/td&gt;
&lt;td&gt;Use DTOs, not full entities&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collections (List, Map)&lt;/td&gt;
&lt;td&gt;Medium (element-by-element)&lt;/td&gt;
&lt;td&gt;Use arrays when possible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deep object graphs&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Flatten or use DTOs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exceptions&lt;/td&gt;
&lt;td&gt;High (stack trace construction)&lt;/td&gt;
&lt;td&gt;Use error codes for expected failures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  DTO Pattern for Cross-Runtime Data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .NET DTO — flat, minimal fields&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;TradeRequest&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;Symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;decimal&lt;/span&gt; &lt;span class="n"&gt;Price&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;Side&lt;/span&gt;  &lt;span class="c1"&gt;// "BUY" or "SELL"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Java DTO — mirrors .NET structure&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;TradeRequest&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;side&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;&lt;strong&gt;Key optimizations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep DTOs flat (no nested objects when avoidable)&lt;/li&gt;
&lt;li&gt;Use primitive types and strings over complex objects&lt;/li&gt;
&lt;li&gt;Avoid passing Java-specific types (HashMap internals, Stream objects) across the bridge&lt;/li&gt;
&lt;li&gt;For large datasets: pass byte arrays and deserialize on the receiving side&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Connection and Resource Pooling
&lt;/h2&gt;

&lt;h3&gt;
  
  
  JVM Instance Reuse
&lt;/h3&gt;

&lt;p&gt;Never create multiple JVM instances per request:&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;// Singleton pattern for bridge initialization&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JavaBridge&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_instance&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;=&amp;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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;JavaBridge&lt;/span&gt; &lt;span class="n"&gt;Instance&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;JavaBridge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;JNBridge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// One-time (1-3 seconds)&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;
  
  
  Object Pooling for Frequently Used Java Objects
&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;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ObjectPool&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JavaPdfParser&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_parserPool&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;DefaultObjectPool&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;JavaPdfParser&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;JavaPdfParserPoolPolicy&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;maxRetained&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;ConvertPdf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&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="p"&gt;{&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="n"&gt;_parserPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&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;return&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;Convert&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="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;_parserPool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Return&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Profiling Tools
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;Free?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BenchmarkDotNet&lt;/td&gt;
&lt;td&gt;.NET&lt;/td&gt;
&lt;td&gt;Microbenchmarks, memory allocation&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dotnet-trace / dotnet-counters&lt;/td&gt;
&lt;td&gt;.NET&lt;/td&gt;
&lt;td&gt;Runtime diagnostics, GC events&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JDK Flight Recorder (JFR)&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Low-overhead production profiling&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;async-profiler&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;CPU + allocation profiling, flame graphs&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VisualVM&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Heap analysis, thread monitoring&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OpenTelemetry&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;td&gt;Distributed tracing across runtimes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prometheus + Grafana&lt;/td&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;td&gt;Metrics dashboards, alerting&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Recommended Workflow
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with OpenTelemetry tracing&lt;/strong&gt; — instrument bridge calls with spans&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable GC logging on both runtimes&lt;/strong&gt; — check for correlated pauses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run BenchmarkDotNet microbenchmarks&lt;/strong&gt; — isolate bridge overhead&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use JFR in production&lt;/strong&gt; — low overhead (&amp;lt;2%) continuous profiling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build a Grafana dashboard&lt;/strong&gt; — track P50/P95/P99 latency over time&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Benchmarks: Before and After
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1000 individual calls&lt;/td&gt;
&lt;td&gt;10ms&lt;/td&gt;
&lt;td&gt;0.05ms&lt;/td&gt;
&lt;td&gt;200x&lt;/td&gt;
&lt;td&gt;Batch call pattern&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex object marshaling&lt;/td&gt;
&lt;td&gt;500µs&lt;/td&gt;
&lt;td&gt;50µs&lt;/td&gt;
&lt;td&gt;10x&lt;/td&gt;
&lt;td&gt;DTO flattening&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P99 latency (GC spikes)&lt;/td&gt;
&lt;td&gt;200ms&lt;/td&gt;
&lt;td&gt;2ms&lt;/td&gt;
&lt;td&gt;100x&lt;/td&gt;
&lt;td&gt;ZGC + Server GC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start (first call)&lt;/td&gt;
&lt;td&gt;5s&lt;/td&gt;
&lt;td&gt;1.5s&lt;/td&gt;
&lt;td&gt;3.3x&lt;/td&gt;
&lt;td&gt;Eager class loading + tiered compilation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrent throughput&lt;/td&gt;
&lt;td&gt;5K calls/s&lt;/td&gt;
&lt;td&gt;50K calls/s&lt;/td&gt;
&lt;td&gt;10x&lt;/td&gt;
&lt;td&gt;Thread pool tuning + object pooling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TCP mode overhead&lt;/td&gt;
&lt;td&gt;0.5ms/call&lt;/td&gt;
&lt;td&gt;5µs/call&lt;/td&gt;
&lt;td&gt;100x&lt;/td&gt;
&lt;td&gt;Switch to shared memory mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Most impactful:&lt;/strong&gt; Switching from chatty calls to batch calls. Almost always the biggest win.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What's the typical overhead of a JNBridgePro bridge call?&lt;/strong&gt;&lt;br&gt;
A single call with simple parameters takes 1–50µs in shared memory mode. For comparison, a REST API call to the same method on localhost: 5–50ms — 1000x slower.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared memory or TCP mode?&lt;/strong&gt;&lt;br&gt;
Use shared memory when Java and .NET run on the same machine. It eliminates network latency entirely (5µs vs 0.5ms per call). TCP mode is only for when JVM and CLR are on different machines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I prevent JVM GC from blocking .NET?&lt;/strong&gt;&lt;br&gt;
Use ZGC (Java 17+) or Shenandoah for sub-millisecond pauses. On .NET, enable Server GC with concurrent mode. Monitor both GC logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I make bridge calls async?&lt;/strong&gt;&lt;br&gt;
Bridge calls are synchronous by design (direct method invocation). Wrap in &lt;code&gt;Task.Run()&lt;/code&gt; for fire-and-forget, or use a producer-consumer queue where a background thread makes bridge calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many concurrent calls can it handle?&lt;/strong&gt;&lt;br&gt;
No hard limit. With proper thread pool tuning, production systems handle 50,000+ calls/sec. The bottleneck is almost always business logic, not bridge overhead.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was originally published at &lt;a href="https://jnbridge.com/jnbridgepro/java-dotnet-performance-tuning-reducing-latency" rel="noopener noreferrer"&gt;jnbridge.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>java</category>
      <category>performance</category>
      <category>csharp</category>
    </item>
  </channel>
</rss>
