Java Client
The Java client workspace contains the MXAccess Gateway client library, generated protobuf/gRPC bindings, a Picocli test CLI project, and JUnit tests.
Layout
clients/java/
settings.gradle
build.gradle
src/main/generated/
zb-mom-ww-mxgateway-client/
zb-mom-ww-mxgateway-cli/
zb-mom-ww-mxgateway-client generates Java protobuf and gRPC sources from
../../src/ZB.MOM.WW.MxGateway.Contracts/Protos. The Gradle protobuf plugin writes those
generated sources under src/main/generated, which matches the client proto
manifest in ../proto/proto-inputs.json. Do not edit generated files by hand.
zb-mom-ww-mxgateway-client exposes MxGatewayClientOptions, MxGatewayClient,
MxGatewaySession, value/status helpers, typed gateway exceptions, raw
generated stubs, and generated protobuf messages for parity tests.
zb-mom-ww-mxgateway-cli depends on zb-mom-ww-mxgateway-client and provides
the mxgw-java application entry point. The CLI supports version, session,
command, event streaming, write, and smoke-test commands with deterministic
JSON output.
Regenerating Protobuf Bindings
Run generation from clients/java after the shared .proto files or Java
output path changes:
gradle :zb-mom-ww-mxgateway-client:generateProto
Client Usage
Create a client with explicit transport and auth options:
MxGatewayClientOptions options = MxGatewayClientOptions.builder()
.endpoint("localhost:5000")
.apiKey(System.getenv("MXGATEWAY_API_KEY"))
.plaintext(true)
.build();
try (MxGatewayClient client = MxGatewayClient.connect(options);
MxGatewaySession session = client.openSession("java-client")) {
int serverHandle = session.register("java-client");
int itemHandle = session.addItem(serverHandle, "TestObject.TestInt");
session.advise(serverHandle, itemHandle);
session.write(serverHandle, itemHandle, MxValues.int32Value(123), 0);
}
Use rawBlockingStub, rawFutureStub, rawAsyncStub, openSessionRaw,
closeSessionRaw, invoke, and raw session helper methods when tests need the
underlying protobuf messages. MxGatewayCommandException and
MxAccessException preserve the raw MxCommandReply when the gateway returns a
data-bearing MXAccess failure.
MxEventStream implements Iterator<MxEvent> and AutoCloseable. Closing it
cancels the underlying gRPC stream. Canceling or timing out a Java client call
only stops the client from waiting; it does not abort an in-flight MXAccess COM
call on the worker STA.
For alarms, MxGatewayClient exposes queryActiveAlarms (one-shot snapshot),
streamAlarms (returns an MxGatewayAlarmFeedSubscription whose iterator
yields alarm-feed messages from the gateway's central monitor), and
acknowledgeAlarm (ack by full alarm reference with an optional comment and
ack target). Close the subscription to cancel the underlying gRPC stream.
Galaxy Repository Browse
The Galaxy Repository service is a separate metadata-only gRPC service exposed
by the gateway. It lets clients enumerate the deployed Galaxy object hierarchy
and the dynamic attributes on each object so they know which tag references to
subscribe to via the MXAccess Gateway service. It uses the same API-key auth as
the gateway and requires the metadata:read scope.
GalaxyRepositoryClient mirrors the MxGatewayClient pattern (caller-managed
or owned channel, MxGatewayClientOptions, blocking + async variants). Three
RPCs are exposed:
MxGatewayClientOptions options = MxGatewayClientOptions.builder()
.endpoint("localhost:5000")
.apiKey(System.getenv("MXGATEWAY_API_KEY"))
.plaintext(true)
.build();
try (GalaxyRepositoryClient galaxy = GalaxyRepositoryClient.connect(options)) {
boolean ok = galaxy.testConnection();
Optional<Instant> lastDeploy = galaxy.getLastDeployTime();
List<GalaxyObject> hierarchy = galaxy.discoverHierarchy();
}
getLastDeployTime returns Optional.empty() when the server reports
present=false. discoverHierarchy returns the generated GalaxyObject proto
messages directly so callers can read all fields (including the nested
GalaxyAttribute list) without an extra DTO layer.
The CLI exposes matching subcommands: galaxy-test, galaxy-deploy-time,
galaxy-discover, and galaxy-watch. They take the same --endpoint,
--api-key-env, --plaintext, --ca-file, --server-name-override,
--timeout, and --json options as the gateway commands.
gradle :zb-mom-ww-mxgateway-cli:run --args="galaxy-test --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --json"
gradle :zb-mom-ww-mxgateway-cli:run --args="galaxy-deploy-time --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --json"
gradle :zb-mom-ww-mxgateway-cli:run --args="galaxy-discover --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --json"
Browsing lazily
For UI trees or OPC UA bridges, use browseChildren to walk one level at a
time instead of loading the full hierarchy with discoverHierarchy. Pass a
default request for root objects; subsequent calls set parentGobjectId,
parentTagName, or parentContainedPath. Filter fields match
DiscoverHierarchy. Each response pairs getChildrenList() with
getChildHasChildrenList() so you know which nodes to expand. See
Galaxy Repository for full
request and filter semantics. This snippet documents the API as it appears once
the Java client is regenerated on the Windows host.
BrowseChildrenReply reply = galaxy.browseChildren(
BrowseChildrenRequest.newBuilder().build());
List<GalaxyObject> children = reply.getChildrenList();
List<Boolean> hasChildren = reply.getChildHasChildrenList();
for (int i = 0; i < children.size(); i++) {
System.out.printf("%s expand=%b%n", children.get(i).getTagName(), hasChildren.get(i));
}
High-level walker
For UI trees, the client provides a LazyBrowseNode walker that handles
sibling pagination and the child_has_children hint for you:
MxGatewayClientOptions options = MxGatewayClientOptions.builder()
.endpoint("localhost:5000")
.apiKey(System.getenv("MXGATEWAY_API_KEY"))
.plaintext(true)
.build();
try (GalaxyRepositoryClient galaxy = GalaxyRepositoryClient.connect(options)) {
List<LazyBrowseNode> roots = galaxy.browse();
for (LazyBrowseNode root : roots) {
if (root.hasChildrenHint()) {
root.expand();
}
for (LazyBrowseNode child : root.getChildren()) {
String kind = child.hasChildrenHint() ? "has children" : "leaf";
System.out.println(child.getObject().getTagName() + " (" + kind + ")");
}
}
}
expand is idempotent — calling it twice fires only one RPC,
and is safe under concurrent callers. To refresh after a Galaxy redeploy, call
browse again from the root.
Watching deploy events
GalaxyRepository.WatchDeployEvents is a server-streaming RPC: the gateway
sends a bootstrap DeployEvent immediately on subscribe and then one event
each time it observes a new galaxy.time_of_last_deploy. The sequence field
is monotonic per server start; gaps mean the per-subscriber buffer dropped
older events because the consumer was too slow.
The client exposes both an iterator-style adaptor over the async stub and an
observer-callback variant. Both honour the channel-level streamTimeout.
try (GalaxyRepositoryClient galaxy = GalaxyRepositoryClient.connect(options);
DeployEventStream events = galaxy.watchDeployEvents(/* lastSeenDeployTime */ null)) {
while (events.hasNext()) {
DeployEvent event = events.next();
// event.getSequence(), event.getObservedAt(),
// event.getTimeOfLastDeploy() / getTimeOfLastDeployPresent(),
// event.getObjectCount(), event.getAttributeCount()
}
}
Pass an Instant for lastSeenDeployTime to suppress the bootstrap event when
the cached deploy time matches what the caller already has. DeployEventStream
implements Iterator<DeployEvent> and AutoCloseable; closing it cancels the
underlying gRPC call.
For callback delivery (e.g. when the consumer wants to drive a queue or reactive pipeline), use the async variant:
DeployEventSubscription subscription = galaxy.watchDeployEventsAsync(
lastSeen,
new StreamObserver<>() {
@Override public void onNext(DeployEvent value) { /* ... */ }
@Override public void onError(Throwable t) { /* ... */ }
@Override public void onCompleted() { /* ... */ }
});
// later:
subscription.cancel(); // or subscription.close()
The matching CLI subcommand streams events until cancelled (Ctrl+C) and prints
one line per event in text mode or one JSON object per event with --json:
gradle :zb-mom-ww-mxgateway-cli:run --args="galaxy-watch --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --json"
gradle :zb-mom-ww-mxgateway-cli:run --args="galaxy-watch --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --last-seen-deploy-time 2026-04-28T18:30:00Z --limit 5"
CLI Usage
Run the CLI through Gradle:
gradle :zb-mom-ww-mxgateway-cli:run --args="version --json"
gradle :zb-mom-ww-mxgateway-cli:run --args="open-session --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --client-session-name java-cli --json"
gradle :zb-mom-ww-mxgateway-cli:run --args="register --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <id> --client-name java-cli --json"
gradle :zb-mom-ww-mxgateway-cli:run --args="add-item --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <id> --server-handle 1 --item TestObject.TestInt --json"
gradle :zb-mom-ww-mxgateway-cli:run --args="advise --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <id> --server-handle 1 --item-handle 1 --json"
gradle :zb-mom-ww-mxgateway-cli:run --args="write --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <id> --server-handle 1 --item-handle 1 --type int32 --value 123 --json"
gradle :zb-mom-ww-mxgateway-cli:run --args="stream-events --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --session-id <id> --limit 1 --json"
gradle :zb-mom-ww-mxgateway-cli:run --args="stream-alarms --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --filter-prefix Galaxy --limit 1 --json"
gradle :zb-mom-ww-mxgateway-cli:run --args="acknowledge-alarm --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --reference \"\\Galaxy\Area001.Pump001.PumpFault\" --json"
gradle :zb-mom-ww-mxgateway-cli:run --args="smoke --endpoint localhost:5000 --api-key-env MXGATEWAY_API_KEY --plaintext --item TestObject.TestInt --json"
The CLI accepts --api-key, --api-key-env, --plaintext, --ca-file,
--server-name-override, --timeout, and --json on gateway commands. JSON
output redacts API keys.
Use TLS options for a secured gateway:
gradle :zb-mom-ww-mxgateway-cli:run --args="smoke --endpoint mxgateway.example.local:5001 --ca-file C:\certs\mxgateway-ca.pem --server-name-override mxgateway.example.local --api-key-env MXGATEWAY_API_KEY --item TestObject.TestInt --json"
Build And Test
Run the Java checks from clients/java:
gradle test
The build uses the Java 21 Gradle toolchain, compiles generated protobuf/gRPC code, and runs JUnit 5 tests for the client wrapper, shared behavior fixtures, in-process gRPC behavior, stream cancellation, and CLI parser/output behavior.
Packaging
Create local library and CLI artifacts from clients/java:
gradle :zb-mom-ww-mxgateway-client:jar :zb-mom-ww-mxgateway-cli:installDist
The library jar is under zb-mom-ww-mxgateway-client/build/libs. The installed CLI
distribution is under zb-mom-ww-mxgateway-cli/build/install/zb-mom-ww-mxgateway-cli.
Integration Checks
Run live checks only when a gateway and MXAccess-backed worker are available:
$env:MXGATEWAY_INTEGRATION = '1'
$env:MXGATEWAY_ENDPOINT = 'localhost:5000'
$env:MXGATEWAY_API_KEY = '<gateway-api-key>'
$env:MXGATEWAY_TEST_ITEM = 'TestObject.TestInt'
gradle :zb-mom-ww-mxgateway-cli:run --args="smoke --endpoint $env:MXGATEWAY_ENDPOINT --plaintext --api-key-env MXGATEWAY_API_KEY --item $env:MXGATEWAY_TEST_ITEM --json"