![]() |
VOOZH | about |
dotnet add package MASES.JNet --version 2.6.9
NuGet\Install-Package MASES.JNet -Version 2.6.9
<PackageReference Include="MASES.JNet" Version="2.6.9" />
<PackageVersion Include="MASES.JNet" Version="2.6.9" />Directory.Packages.props
<PackageReference Include="MASES.JNet" />Project file
paket add MASES.JNet --version 2.6.9
#r "nuget: MASES.JNet, 2.6.9"
#:package MASES.JNet@2.6.9
#addin nuget:?package=MASES.JNet&version=2.6.9Install as a Cake Addin
#tool nuget:?package=MASES.JNet&version=2.6.9Install as a Cake Tool
JNet exposes Java™ classes directly in .NET, letting you write C# code against the same types available in the official Java™ packages. If a class or method has not been mapped yet, see .
JNet accepts many command-line switches to customize its behavior. The full list is available at the page.
One of the most important command-line switches is JVMPath, available in JCOBridge switches: it can be used to set the location of the JVM™ library (jvm.dll / libjvm.so) if JCOBridge is not able to identify a suitable JRE installation.
If you are embedding JNet in your own product, you can override the JVMPath property as shown below:
class MyJNetCore : JNetCore<MyJNetCore>
{
// Override JVMPath when JCOBridge cannot auto-detect the JRE/JDK installation,
// or when you need to pin a specific JVM version in your application.
public override string JVMPath
{
get
{
string pathToJVM = "Set here the path to the JVM library (jvm.dll / libjvm.so)";
return pathToJVM;
}
}
}
pathToJVM must be properly escaped:
string pathToJVM = "C:\\Program Files\\Eclipse Adoptium\\jdk-11.0.18.10-hotspot\\bin\\server\\jvm.dll";string pathToJVM = @"C:\Program Files\Eclipse Adoptium\jdk-11.0.18.10-hotspot\bin\server\jvm.dll";JCOBridge attempts to locate a suitable JRE/JDK installation using standard mechanisms: the JAVA_HOME environment variable or the Windows registry (where available).
If the application fails with InvalidOperationException: Missing Java Key in registry, neither JAVA_HOME nor the Windows registry contains a reference to a JRE/JDK installation.
Diagnose the issue:
set | findstr JAVA_HOME.Fix the issue (choose one):
JAVA_HOME at system level, e.g. JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-11.0.18.10-hotspot\JCOBRIDGE_JVMPath at system level to point directly to the JVM library, e.g. JCOBRIDGE_JVMPath=C:\Program Files\Eclipse Adoptium\jdk-11.0.18.10-hotspot\bin\server\jvm.dllJCOBRIDGE_JVMPath, JAVA_HOME, or the Windows registry (on Windows) must be available.JCOBRIDGE_JVMPath takes precedence over JAVA_HOME and the Windows registry: setting it to the full path of jvm.dll avoids the need to override JVMPath in code.JVMPath (set in code) takes precedence over both environment variables and the registry.JNet uses an embedded JVM™ through JCOBridge. However, JVM™ initialization is incompatible with CET (Control-flow Enforcement Technology) because the code used to identify the CPU attempts to modify the return address, which CET treats as a violation — see this issue comment.
From .NET 9 preview 6, CET is enabled by default on supported hardware when the build output is an executable (i.e. the .csproj contains <OutputType>Exe</OutputType>).
If the application fails at startup with error 0xc0000409 (subcode 0x30), CET is enabled and conflicting with JVM™ initialization.
Solutions 2 and 3 are the recommended approaches for most projects. Solution 1 requires targeting an older .NET version; solution 4 requires elevated privileges and a registry change.
There are four possible workarounds:
Target a .NET version that does not enable CET by default, such as .NET 8.
Disable CET for the executable in the .csproj (JNet project templates include this automatically):
<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0'">
<CETCompat>false</CETCompat>
</PropertyGroup>
dotnet app host instead of the native executable, as described in this comment:dotnet MyApplication.dll
instead of:
MyApplication.exe
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\MyApplication.exe" /v MitigationOptions /t REG_BINARY /d "0000000000000000000000000000002000" /f
then run:
MyApplication.exe
Below is a basic example demonstrating how to create a JNet-based program, including generics and exception handling. Comments in the code explain each step.
using Java.Util;
using MASES.JNet.Extensions;
using System.Diagnostics;
using Java.Lang;
namespace MASES.JNetExample
{
// Define a concrete implementation of JNetCore<> for this application.
class MyJNetCore : JNetCore<MyJNetCore>
{
}
class Program
{
static void Main(string[] args)
{
// Mandatory first step: allocate the JVM and prepare the interop environment.
MyJNetCore.CreateGlobalInstance();
// Arguments not consumed by JNet/JCOBridge are available here,
// just like standard command-line args.
var appArgs = MyJNetCore.FilteredArgs;
try
{
// Allocate a java.util.Set<String> in the JVM via Collections.Singleton,
// returned as a Java.Util.Set<string> on the .NET side.
Java.Util.Set<string> set = Collections.Singleton("test");
// Attempt to add an element if one was passed on the command line.
// Collections.Singleton returns an immutable Set, so this will throw.
// See: https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#singleton(T)
if (appArgs.Length != 0) set.Add(appArgs[0]);
}
// JNet translates Java exceptions into equivalent .NET exceptions,
// so UnsupportedOperationException is caught here just like any C# exception.
catch (UnsupportedOperationException)
{
System.Console.WriteLine("Operation not supported as expected");
}
// Catch-all: print any unexpected exception and let the application exit cleanly.
catch (System.Exception ex) { System.Console.WriteLine(ex.Message); }
}
}
}
Java.Lang.NullPointerException — Understanding .NET/JVM GC interactionOccasionally, a Java.Lang.NullPointerException is raised with no obvious cause in the .NET code. This is a cross-boundary GC issue: the .NET Garbage Collector may collect a JNet wrapper object while the JVM™ is still using the underlying Java object it references.
In the basic example above, Collections.Singleton("test") creates a wrapper held by set, which remains reachable until set.Add(appArgs[0]) completes — so the GC does not collect it prematurely.
Consider this slightly different snippet:
using Java.Util;
using MASES.JNet.Extensions;
using System.Diagnostics;
using Java.Lang;
namespace MASES.JNetExample
{
class MyJNetCore : JNetCore<MyJNetCore> { }
class Program
{
static void Main(string[] args)
{
MyJNetCore.CreateGlobalInstance();
try
{
Java.Util.Set<string> set = Collections.Singleton("test");
ArrayList<string> arrayList = new();
arrayList.AddAll(0, set); // Java.Lang.NullPointerException may occur here
}
catch (System.Exception ex) { System.Console.WriteLine(ex.Message); }
}
}
}
At the point arrayList.AddAll(0, set) is called:
Java.Util.Set<string> is a .NET wrapper around a JVM™ java.util.Set<String>.set has no further uses and is eligible for collection.Most of the time the code works fine, but the failure is non-deterministic and hard to reproduce.
using or try-finally with DisposeAll JNet classes implement IDisposable. Wrapping an object in a using block keeps it alive for the entire scope and releases the JVM™ global reference deterministically when the block exits:
using Java.Util;
using MASES.JNet.Extensions;
using Java.Lang;
namespace MASES.JNetExample
{
class MyJNetCore : JNetCore<MyJNetCore> { }
class Program
{
static void Main(string[] args)
{
MyJNetCore.CreateGlobalInstance();
try
{
using (var set = Collections.Singleton("test"))
{
ArrayList<string> arrayList = new();
arrayList.AddAll(0, set);
}
}
catch (System.Exception ex) { System.Console.WriteLine(ex.Message); }
}
}
}
Or equivalently with try-finally:
Java.Util.Set<string> set = Collections.Singleton("test");
try
{
ArrayList<string> arrayList = new();
arrayList.AddAll(0, set);
}
finally { set?.Dispose(); }
The using pattern is the most idiomatic approach in modern C# and should be preferred in new code.
SuppressFinalize/ReRegisterForFinalize patternWhen restructuring to using is not practical, you can suppress finalization for the duration of the cross-boundary call:
Java.Util.Set<string> set = Collections.Singleton("test");
try
{
System.GC.SuppressFinalize(set);
ArrayList<string> arrayList = new();
arrayList.AddAll(0, set);
}
finally { System.GC.ReRegisterForFinalize(set); }
Each Dispose on a JNet object releases the underlying JVM™ global reference with a direct native call. In tight loops that create and dispose many JNet objects — such as a Kafka consumer poll loop or a storage enumeration — this per-object native call cost accumulates.
JCOBridgeDisposeFastScope and JCOBridgeDisposeAsyncScope address this by batching the releases and flushing them in a single native call, keeping the loop body unchanged.
JCOBridgeDisposeFastScope — synchronous hot pathsUse this scope in synchronous code on a controlled thread. It uses thread-local storage with minimal access cost.
using var batch = new JCOBridgeDisposeFastScope();
while (!resetEvent.WaitOne(0))
{
using var records = consumer.Poll(200);
foreach (var record in records)
{
using (record)
{
Console.WriteLine($"Offset={record.Offset()}, Key={record.Key()}");
}
}
} // all queued releases flushed here in a single native call
JCOBridgeDisposeFastScope is not safe across await — if a continuation resumes on a different thread the scope state will not be visible. Use JCOBridgeDisposeAsyncScope for async code.
JCOBridgeDisposeAsyncScope — async/await contextsUse this scope when continuations may resume on a different thread. The scope state flows automatically across await points.
On .NET 8 and later JCOBridgeDisposeAsyncScope implements IAsyncDisposable, enabling await using and an asynchronous flush on scope exit:
// .NET 8 / 9 / 10 — IAsyncDisposable available
await using var batch = new JCOBridgeDisposeAsyncScope();
await foreach (var item in asyncCollection)
{
using (item)
{
await ProcessAsync(item);
}
} // queued releases flushed asynchronously when the scope exits
On .NET Framework IAsyncDisposable is not available. Use a standard using block — the flush on scope exit is synchronous:
// .NET Framework — IDisposable only
using (var batch = new JCOBridgeDisposeAsyncScope())
{
foreach (var item in collection)
{
using (item) { /* item disposal is batched */ }
}
} // queued releases flushed synchronously when the scope exits
Both scopes are opt-in and additive — code that does not open a scope continues to release references immediately on Dispose, with no behavioral change. Scopes are designed to be opened by library code (e.g. storage enumerators, consumer loops) rather than exposed to end users.
See for guidance on when batch scopes provide meaningful gains.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 is compatible. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
| .NET Framework | net462 net462 is compatible. net463 net463 was computed. net47 net47 was computed. net471 net471 was computed. net472 net472 was computed. net48 net48 was computed. net481 net481 was computed. |
Showing the top 5 NuGet packages that depend on MASES.JNet:
| Package | Downloads |
|---|---|
|
MASES.KNet
Core of .NET suite for Apache Kafka. KNet is a comprehensive .NET suite for Apache Kafka providing all features: Producer, Consumer, Admin, Streams, Connect, backends (KRaft). |
|
|
MASES.PLCOnNet
.NET suite for PLC4X - a comprehensive suite of libraries and tools to use PLC4X and .NET side-by-side |
|
|
MASES.NetPDF
.NET suite for PDFBox™ - a comprehensive suite of libraries and tools to use PDFBox™ and .NET side-by-side |
|
|
MASES.Naven
.NET suite for Apache Maven™ - a comprehensive suite of libraries and tools to use Apache Maven™ and .NET side-by-side |
|
|
MASES.JNetPSCore
JNetPSCore - JNet (.NET suite for Java™/JVM™) PowerShell base library |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.6.9 | 535 | 6/15/2026 |
| 2.6.9-rc996 | 6,077 | 6/9/2026 |
| 2.6.9-rc995 | 374 | 6/7/2026 |