Skip to content

Kotlin Channel Messaging Public Interface

Interface table of contents · Java Channel

A Kotlin application doesn't directly use the Java Channel call. A Kotlin-only client and call wrapper keep the Java call internally, projecting regular completion as await(), and completion that returns the current Spot turn as yield(). One-way await()'s normal result is Unit, and failure propagates the Java stage's exception unchanged. The typed request's reified entry method fixes KClass<TReply>.java as the internal Java Class<TReply> when building the wrapper. The application doesn't pass the reply type again at the terminal, or write .submit().await().

The synchronous blocking terminator adds no Kotlin-specific wrapper and exposes the Java surface's submit_sync() as is. A blocking call is not a suspend, so it is not wrapped in await(); it is for application threads and fails with InvalidOperation when called from a runtime execution context (Submit And Completion §4 F2-a).

The Kotlin filter uses the same applicable scope as the Java runtime. It's applied to RouteMesh/ClientServer Channel send/request, Node direct send/request, and classic fanout subscription handlers, and isn't applied to Spot/Actor/Logical Multicast/STREAM handlers. ZLinkHandlerFilterContext.dispatchKind() distinguishes the five paths. RouteMesh and Node direct provide MeshName, and ClientServer and classic fanout don't.

The handler and filter are each created once per dispatch and use the same scoped dependency — application DI registration can't change this lifetime. Calling next.invoke() twice is IllegalStateException, and not calling it on a request is REJECTED. Even if the filter returns an arbitrary value, it doesn't substitute for the handler reply. Coroutine suspension doesn't extend the dispatch scope beyond terminal completion.

A Spot direct send/request isn't reduced to a Channel call. Since a Kotlin-only Spot wrapper configures instanceSpot and inMesh before terminal await()/yield(), it keeps the fluent state of Missing Instance cold activation. Kotlin's Spot-dedicated wrapper and JVM signature are owned by the Spot Interface.

Kotlin Source Signature

interface ZLinkSuspendingRequestHandler<TRequest, TReply> {
 suspend fun handle(request: TRequest, context: ZLinkMessageContext): TReply
}

interface ZLinkSuspendingSendHandler<TMessage> {
 suspend fun handle(message: TMessage, context: ZLinkMessageContext)
}

interface ZLinkSuspendingPublishHandler<TMessage> {
 suspend fun handle(message: TMessage, context: ZLinkPublishMessageContext)
}

interface ZLinkSuspendingRouteRequestHandler<TRequest, TReply> {
 suspend fun handle(request: TRequest, context: ZLinkRouteMessageContext): TReply
}

interface ZLinkSuspendingRouteSendHandler<TMessage> {
 suspend fun handle(message: TMessage, context: ZLinkRouteMessageContext)
}

interface ZLinkKotlinMessageSendCall {
 fun metadata(key: String, value: String): ZLinkKotlinMessageSendCall
 suspend fun await()
}

interface ZLinkKotlinSubmissionCall {
 suspend fun await()
}

interface ZLinkKotlinRequestCall<TReply> {
 fun metadata(key: String, value: String): ZLinkKotlinRequestCall<TReply>
 fun timeout(timeout: Duration): ZLinkKotlinRequestCall<TReply>
 suspend fun await(): TReply
 suspend fun yield(): TReply
}

interface ZLinkKotlinClient {
 fun sendToChannel(
 channelName: String,
 message: Any,
 ): ZLinkKotlinMessageSendCall

 fun <TReply : Any> requestToChannel(
 channelName: String,
 request: Any,
 replyType: KClass<TReply>,
 ): ZLinkKotlinRequestCall<TReply>
}

inline fun <reified TReply : Any> ZLinkKotlinClient.requestToChannel(
 channelName: String,
 request: Any,
): ZLinkKotlinRequestCall<TReply> =
 requestToChannel(channelName, request, TReply::class)

interface ZLinkKotlinFanoutClient {
 fun publish(
 channelName: String,
 topic: String,
 event: Any,
 ): ZLinkKotlinSubmissionCall
 fun publish(
 channelName: String,
 event: Any,
 ): ZLinkKotlinSubmissionCall
}

interface ZLinkKotlinRouteClient {
 fun sendToNode(
 meshName: String,
 target: RoutingId,
 message: Any,
 ): ZLinkKotlinMessageSendCall

 fun <TReply : Any> requestToNode(
 meshName: String,
 target: RoutingId,
 request: Any,
 replyType: KClass<TReply>,
 ): ZLinkKotlinRequestCall<TReply>

 fun sendToChannel(
 channelName: String,
 message: Any,
 ): ZLinkKotlinMessageSendCall

 fun <TReply : Any> requestToChannel(
 channelName: String,
 request: Any,
 replyType: KClass<TReply>,
 ): ZLinkKotlinRequestCall<TReply>
}

inline fun <reified TReply : Any> ZLinkKotlinRouteClient.requestToNode(
 meshName: String,
 target: RoutingId,
 request: Any,
): ZLinkKotlinRequestCall<TReply> =
 requestToNode(meshName, target, request, TReply::class)

inline fun <reified TReply : Any> ZLinkKotlinRouteClient.requestToChannel(
 channelName: String,
 request: Any,
): ZLinkKotlinRequestCall<TReply> =
 requestToChannel(channelName, request, TReply::class)

public inline fun <reified T : Any> messageOf(value: T): ZLinkMessage
public fun messageOf(value: Any, declaredType: KClass<*>): ZLinkMessage
public inline fun <reified T> ZLinkMessage.decode(): T

The first messageOf(...) overload retains the call site's T as the message's declared type. The codec selector uses that declared type even when the runtime value is a subtype. Use the second overload when the call site cannot retain T, such as Java reflection or a shared base-type boundary.

The first decode<T>() on a received message fixes either a value or a failure. Later calls use the same single outcome as Java's ZLinkMessage.decode(Class<T>) and do not invoke the serializer again. Another T ends with TYPE_MISMATCH when it cannot accept the first value, and a failure from the first call is delivered again.

ZLinkKotlinRequestCall.yield() is only a coroutine bridge for Java's yield(...) — it doesn't turn an arbitrary suspension into Yield. If it's not a SPOT_WIDE User Spot or Instance Spot application handler, it completes with InvalidOperation before suspending the coroutine or submitting the underlying operation. The same rule applies to a Node direct request, Entry/PER_ACTOR, Channel handler, and outside the owner context. A regular await() that waits on a target needing the current Spot gate is also rejected before submission. The one-way wrapper keeps FIFO queue admission and doesn't call the handler inline or reentrantly.

If the queue is full, it waits until the send timeout. Timeout completes with DeadlineExceeded, a route break with Unavailable, and runtime shutdown with ShuttingDown. Absence of target or session binding is NotFound. If cancellation is triggered first, it completes as coroutine cancellation.

Passing the internal liveness-dedicated byte 01 5A 4C 46 31 to publishToTopic(...), which takes a topic, raises the Java runtime's ZLinkConfigurationException without starting transport. The overload that omits topic uses the typed event's packet name, so it doesn't create this internal topic.

The RouteMesh DSL doesn't change the Java builder's meaning — it only provides a receiver and lambda. It configures a per-ChannelName role on one MeshNode's physical connection.

The Kotlin runtime directly uses Java's ZLinkRouteMeshRuntimeOptions. channel(meshName, channelName) selects a target Mesh and ChannelName, while mesh(meshName) selects the placement option. channel(channelName) selects a ChannelName registered on exactly one Mesh. The Kotlin DSL's routeMesh and channel don't add a new overload to this runtime option. So these three methods must be confirmed with the same name, arguments, and return type in both the Java per-language interface and the Kotlin package consumer.

RouteMesh Channel Server and ClientServer Server weight use the Java builder's signed int. The allowed range is 0..10000, defaulting to 100, and 0 is excluded from new target selection. Logical Multicast includes each positive member exactly once, and doesn't increase submit count by weight magnitude. An out-of-range startup/runtime setting is a configuration error. Logical Multicast's remote target is submitted once to the local transport queue of the MeshNode route fixed on the source, and the local target is submitted once to the matching local Spot queue. Per-target success/drop/unreachable results aren't returned as await()'s result or aggregated into public monitoring. Remote Spot queue admission and remote/local handler execution or completion aren't the coroutine bridge's completion condition.

fun ZLinkFrameworkOptions.routeMesh(
 meshName: String,
 configure: ZLinkMeshNodeBuilder.() -> Unit,
): ZLinkMeshNodeBuilder

fun ZLinkMeshNodeBuilder.channel(
 channelName: String,
 configure: ZLinkMeshChannelBuilder.() -> Unit = {},
): ZLinkMeshChannelBuilder

fun ZLinkMeshPeerConnections.connect(
 expectedRoutingId: RoutingId,
 endpoint: String,
)
val reply = routeClient
 .requestToChannel<InventoryReply>("inventory", request)
 .await()

Generated JVM Signature

The JVM signature below is the generated form of the Kotlin source contract.

public final class systems.zlink.framework.kotlin.ZLinkMessageExtensionsKt {
 public static final <T> systems.zlink.framework.messaging.ZLinkMessage messageOf(T);
 public static final systems.zlink.framework.messaging.ZLinkMessage messageOf(java.lang.Object, kotlin.reflect.KClass<?>);
 public static final <T> T decode(systems.zlink.framework.messaging.ZLinkMessage);
}
public final class systems.zlink.framework.kotlin.ZLinkRouteMeshExtensionsKt {
 public static final systems.zlink.framework.configuration.ZLinkMeshNodeBuilder routeMesh(systems.zlink.framework.configuration.ZLinkFrameworkOptions, java.lang.String, kotlin.jvm.functions.Function1<? super systems.zlink.framework.configuration.ZLinkMeshNodeBuilder, kotlin.Unit>);
 public static final systems.zlink.framework.configuration.ZLinkMeshChannelBuilder channel(systems.zlink.framework.configuration.ZLinkMeshNodeBuilder, java.lang.String, kotlin.jvm.functions.Function1<? super systems.zlink.framework.configuration.ZLinkMeshChannelBuilder, kotlin.Unit>);
 public static systems.zlink.framework.configuration.ZLinkMeshChannelBuilder channel$default(systems.zlink.framework.configuration.ZLinkMeshNodeBuilder, java.lang.String, kotlin.jvm.functions.Function1, int, java.lang.Object);
 public static final void connect(systems.zlink.framework.configuration.ZLinkMeshPeerConnections, systems.zlink.contracts.core.RoutingId, java.lang.String);
}
public final class systems.zlink.framework.kotlin.ZLinkSuspendingHandlersKt {
}
public interface systems.zlink.framework.kotlin.ZLinkSuspendingPublishHandler<TMessage> {
 public abstract java.lang.Object handle(TMessage, systems.zlink.framework.channels.ZLinkPublishMessageContext, kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
public interface systems.zlink.framework.kotlin.ZLinkSuspendingRequestHandler<TRequest, TReply> {
 public abstract java.lang.Object handle(TRequest, systems.zlink.framework.ZLinkMessageContext, kotlin.coroutines.Continuation<? super TReply>);
}
public interface systems.zlink.framework.kotlin.ZLinkSuspendingRouteRequestHandler<TRequest, TReply> {
 public abstract java.lang.Object handle(TRequest, systems.zlink.framework.channels.ZLinkRouteMessageContext, kotlin.coroutines.Continuation<? super TReply>);
}
public interface systems.zlink.framework.kotlin.ZLinkSuspendingRouteSendHandler<TMessage> {
 public abstract java.lang.Object handle(TMessage, systems.zlink.framework.channels.ZLinkRouteMessageContext, kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
public interface systems.zlink.framework.kotlin.ZLinkSuspendingSendHandler<TMessage> {
 public abstract java.lang.Object handle(TMessage, systems.zlink.framework.ZLinkMessageContext, kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
public interface systems.zlink.framework.kotlin.ZLinkKotlinMessageSendCall {
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinMessageSendCall metadata(java.lang.String, java.lang.String);
 public abstract java.lang.Object await(kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
public interface systems.zlink.framework.kotlin.ZLinkKotlinSubmissionCall {
 public abstract java.lang.Object await(kotlin.coroutines.Continuation<? super kotlin.Unit>);
}
public interface systems.zlink.framework.kotlin.ZLinkKotlinRequestCall<TReply> {
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinRequestCall<TReply> metadata(java.lang.String, java.lang.String);
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinRequestCall<TReply> timeout-LRDsOJo(long);
 public abstract java.lang.Object await(kotlin.coroutines.Continuation<? super TReply>);
 public abstract java.lang.Object yield(kotlin.coroutines.Continuation<? super TReply>);
}
public interface systems.zlink.framework.kotlin.ZLinkKotlinClient {
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinMessageSendCall sendToChannel(java.lang.String, java.lang.Object);
 public abstract <TReply> systems.zlink.framework.kotlin.ZLinkKotlinRequestCall<TReply> requestToChannel(java.lang.String, java.lang.Object, kotlin.reflect.KClass<TReply>);
}
public interface systems.zlink.framework.kotlin.ZLinkKotlinFanoutClient {
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSubmissionCall publish(java.lang.String, java.lang.String, java.lang.Object);
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinSubmissionCall publish(java.lang.String, java.lang.Object);
}
public interface systems.zlink.framework.kotlin.ZLinkKotlinRouteClient {
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinMessageSendCall sendToNode(java.lang.String, systems.zlink.contracts.core.RoutingId, java.lang.Object);
 public abstract <TReply> systems.zlink.framework.kotlin.ZLinkKotlinRequestCall<TReply> requestToNode(java.lang.String, systems.zlink.contracts.core.RoutingId, java.lang.Object, kotlin.reflect.KClass<TReply>);
 public abstract systems.zlink.framework.kotlin.ZLinkKotlinMessageSendCall sendToChannel(java.lang.String, java.lang.Object);
 public abstract <TReply> systems.zlink.framework.kotlin.ZLinkKotlinRequestCall<TReply> requestToChannel(java.lang.String, java.lang.Object, kotlin.reflect.KClass<TReply>);
}