DEV Community

Cover image for Use NI-DAQmx from Java Without Owning a JNI Layer
JNBridge
JNBridge

Posted on Originally published at jnbridge.com

Use NI-DAQmx from Java Without Owning a JNI Layer

If a Java application needs data from National Instruments hardware, the awkward part is rarely the acquisition logic. It is the missing Java API: NI-DAQmx supports C, .NET, Python, and LabVIEW, leaving JVM teams to decide whether to maintain native glue or put another boundary around the driver.

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

Why the obvious JNI route becomes expensive

JNI can call the NI-DAQmx C API, but it makes your team responsible for the entire native boundary.

For every operation you expose, you need to own:

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

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

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

Bridge to the maintained API instead

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

With JNBridgePro, the build-time flow is:

  1. Point the proxy generator at NationalInstruments.DAQmx.dll.
  2. Select the types your application needs.
  3. Generate a JAR containing Java proxies for those .NET types.
  4. Add the proxy JAR and bridge runtime to the Java project.

At runtime, Java calls a proxy. The bridge invokes the real .NET object and returns the result, including arrays and exceptions, across the runtime boundary.

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

What the Java code looks like

Here is a condensed acquisition example. It creates a four-channel voltage task, configures a finite sample clock, and reads 100 samples per channel.

import com.jnbridge.jnbcore.DotNetSide;
import NationalInstruments.DAQmx.*;

public class AnalogInDemo {
    public static void main(String[] args) throws Exception {
        DotNetSide.init("sharedmemory.properties");

        Task task = new Task();
        task.Get_AIChannels().CreateVoltageChannel(
            "Dev1/ai0:3",
            "",
            AITerminalConfiguration.Differential,
            -10.0,
            10.0,
            AIVoltageUnits.Volts
        );

        task.Get_Timing().ConfigureSampleClock(
            "",
            1000.0,
            SampleClockActiveEdge.Rising,
            SampleQuantityMode.FiniteSamples,
            100
        );

        AnalogMultiChannelReader reader =
            new AnalogMultiChannelReader(task.Get_Stream());

        task.Start();
        double[][] data = reader.ReadMultiSample(100);
        task.Stop();
        task.Dispose();

        System.out.printf(
            "Read %d samples on %d channels%n",
            data[0].length,
            data.length
        );
    }
}
Enter fullscreen mode Exit fullscreen mode

Several details are worth calling out:

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

Keep the boundary coarse enough

A bridge removes JNI maintenance, but it does not make boundary design irrelevant. The fastest call is still the call you do not make.

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

The same rule applies to other hardware SDKs:

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

Choose the runtime topology deliberately

There are two useful deployment shapes.

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

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

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

Test without physical hardware

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

A practical validation plan is:

  1. Install NI-DAQmx with .NET support.
  2. Create a simulated device in NI MAX.
  3. Generate proxies from the installed DAQmx assembly.
  4. Run a finite analog-input read.
  5. Test an invalid channel to verify exception translation.
  6. Repeat a block read under realistic rate and channel-count settings.
  7. Replace the simulated device name with the physical device when hardware is available.

The code path does not need to change when moving from a simulated device to the real one.

When a service boundary is still better

Direct bridging is not the answer to every integration problem.

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

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

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

Final takeaway

The missing NI-DAQmx Java API does not force a choice between rewriting the acquisition layer and maintaining JNI glue forever.

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

The full walkthrough, including setup details and the working demo, is in the original NI-DAQmx Java guide.

Top comments (0)