Exercise communication across JVMs¶
Two runtime objects in one JVM and two independent JVM processes are different exercises. Start by seeing actual transport behavior, then configure a registry connection shared by the independently launched applications.
First: actual gRPC between two runtimes¶
./gradlew -I documentation/examples/compile.init.gradle documentationRunRuntime
Expected application output:
RuntimeExample OK: two runtimes, actual gRPC, callback, filtered discovery
The program uses two distinct runtimes and real gRPC, but shares an embedded registry store in one JVM. It checks a request/reply, a caller-owned callback and metadata-filtered discovery. Its development security and disabled administration are explicitly local tutorial settings.
/*
* BSD 4-Clause License
*
* Copyright (c) 2026, Leo Galambos and the Egothor team
* Website of the Egothor team: https://www.egothor.org
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgement:
*
* This product includes software developed by Leo Galambos and the Egothor
* team (https://www.egothor.org).
*
* 4. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.j7m.documentation;
import java.time.Clock;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import org.j7m.middleware.api.ExportOptions;
import org.j7m.middleware.api.LookupOptions;
import org.j7m.middleware.registry.embedded.EmbeddedRegistryClient;
import org.j7m.middleware.registry.embedded.EmbeddedRegistryStore;
import org.j7m.middleware.runtime.MiddlewareRuntimeBuilder;
import org.j7m.middleware.transport.grpc.GrpcTransportProvider;
/**
* Runs real gRPC invocation and callback traffic between two runtimes in one JVM.
*/
public final class RuntimeExample {
private RuntimeExample() { }
/**
* Receives an application callback.
*/
public interface Listener {
/**
* Receives one public result message.
* @param value result message
*/
void onResult(String value);
}
/**
* Business contract exported through the explicit runtime API.
*/
public interface GreetingApi {
/**
* Greets a recipient and invokes the supplied callback by reference.
* @param name recipient name
* @param listener application callback exported by the caller's runtime
* @return greeting
*/
String greet(String name, Listener listener);
}
/**
* Public application implementation; no service or injection annotation is required.
*/
public static final class GreetingService implements GreetingApi {
/**
* Creates the stateless service.
*/
public GreetingService() { }
/**
* {@inheritDoc}
*/
@Override public String greet(String name, Listener listener) {
String result = "Hello, " + name;
listener.onResult(result);
return result;
}
}
/**
* Runs an actual remote call, callback and discovery filter check.
* This isolated tutorial deliberately disables nugget administration and uses development gRPC
* security. Production hosts configure authenticated transport and the administration PIN.
* @param args unused command-line arguments
*/
public static void main(String[] args) {
var store = new EmbeddedRegistryStore();
try (var server = new MiddlewareRuntimeBuilder().runtimeId("docs-server")
.registry(new EmbeddedRegistryClient(store, Clock.systemUTC()))
.addTransport(new GrpcTransportProvider()).disableNuggetAdmin().build();
var client = new MiddlewareRuntimeBuilder().runtimeId("docs-client")
.registry(new EmbeddedRegistryClient(store, Clock.systemUTC()))
.addTransport(new GrpcTransportProvider()).disableNuggetAdmin().build()) {
var options = new ExportOptions(false, "grpc", "default", List.of(), Map.of("region", "local"), null, null);
try (var registration = server.export("greetings", new GreetingService(), GreetingApi.class, options)) {
var received = new CopyOnWriteArrayList<String>();
Listener callback = received::add;
GreetingApi proxy = client.lookup("greetings", GreetingApi.class, LookupOptions.defaults());
require("Hello, Ada".equals(proxy.greet("Ada", callback)), "Remote result changed");
require(received.equals(List.of("Hello, Ada")), "Callback did not return through the caller runtime");
require(client.list("greetings", List.of(GreetingApi.class.getName()), Map.of("region", "other"),
LookupOptions.defaults()).isEmpty(), "Required registry metadata was ignored");
require(!registration.ref().runtimeId().equals(client.identity().runtimeId()), "Call selected the client runtime");
}
}
System.out.println("RuntimeExample OK: two runtimes, actual gRPC, callback, filtered discovery");
}
private static void require(boolean condition, String message) {
if (!condition) throw new AssertionError(message);
}
}
Then: independent registry and application JVMs¶
A JVM-local embedded registry store cannot connect separate processes. Run a host-owned bootstrap endpoint beside the publishing application's registry, and configure the client application to query it over mutual TLS. The following two fragments belong in separate Java entry points; they deliberately leave certificate loading and business transport construction to the host configuration.
Publishing JVM¶
// localRegistry contains this JVM's exported business services.
// serverTls is an SSLContext with the server identity and trusted client issuers.
try (var endpoint = new BootstrapRegistryServer(
new InetSocketAddress("127.0.0.1", 4117),
serverTls, localRegistry, Set.of("orders"), Duration.ofSeconds(2))) {
shutdownSignal.await();
}
shutdownSignal is an application-owned CountDownLatch. The endpoint borrows localRegistry; its owner must keep that registry and the service exports alive until shutdown. Use a host-reachable bind address when registry clients are on another machine.
Discovering JVM¶
try (var resolver = new NetworkBootstrapEndpointResolver(
clientTls, Duration.ofMillis(750));
var registry = new BootstrapRegistryClient(
localRegistry, Clock.systemUTC(),
new BootstrapRegistryOptions(
"order-client", "orders",
List.of("bootstrap+tls://localhost:4117"),
Duration.ofSeconds(2), 3, 16, 64,
Duration.ofSeconds(1), resolver),
recordVerifier, true, auditSink)) {
// Supply registry to this application's MiddlewareRuntimeBuilder.registry(registry).
// Resolve the exported business interface through the resulting runtime.
}
clientTls, recordVerifier and auditSink come from trusted host configuration. The client's certificate must be trusted by the endpoint, and the endpoint certificate must include localhost as a SAN for this tutorial URI. Publisher-record verification remains distinct from TLS authentication. BootstrapRegistryClient owns the local registry it is given; do not give it an independently owned runtime registry that will be closed twice.
The bootstrap classes are in org.j7m.middleware.registry.bootstrap. Import JDK InetSocketAddress, Clock, Duration, List and Set from their standard packages. For a complete certificate-loading fragment and production seed configuration see TLS configuration and discovery configuration.
Same-host scope
The built-in gRPC/RMI providers currently advertise loopback business endpoints. These process exercises run on one host. A reachable registry on another machine does not rewrite a service's advertised address. Cross-host business invocation requires an appropriate transport/host integration; native resource endpoints and network bootstrap have their own explicit bind configuration.