# Setup — one cluster
A Java node and a Node.js node join the same Moleculer cluster when five things line up:
- The same transporter — both connect to one message bus. These examples use NATS at
nats://localhost:4222. A transporter is mandatory for clustering (see the warning below). - The same serializer — JSON on both sides (the default), the common denominator that makes the two frameworks wire-compatible.
- The same protocol version — Moleculer JS 0.15 speaks v5; moleculer-java 2.0.0 defaults to v5, so they match out of the box.
- The same
namespace— both nodes must run in the same namespace (the default is the empty namespace""). The namespace is baked into the transporter's channel names, so nodes in different namespaces never see each other. - A unique
nodeIDper process — every node in the cluster needs its own id; colliding ids break discovery.
Get those right and discovery, routing, request/response, events and streaming all work across the language boundary with no gateway in between.
No transporter = local-only (the #1 "why can't Node.js see my Java node?")
If you never call cfg.setTransporter(...), the broker's transporter stays null by default and the
node starts in local-only mode: it runs, logs no error, and serves only its own in-process
services. A Node.js node will never discover it — it simply won't appear in $node.list. Clustering
begins the moment you attach a transporter, which every example on this page does. (The same is true on
the Node.js side: a broker started with no transporter is isolated.)
# 1. Add the dependency
# 2. Configure the broker
The same four knobs, expressed in each language. On the Java side the service class is identical whether you run under Spring or not — only how you create and start the broker differs (see Runner for more ways to start a Java node). Both Java tabs below register this one service:
// MathJavaService.java — registered unchanged by both Java setups below.
// Actions are INSTANCE fields (lambdas), discovered by reflection — never static.
@Name("mathJava")
public class MathJavaService extends Service {
public Action add = ctx ->
ctx.params.get("a", 0) + ctx.params.get("b", 0);
}
# 3. Start a NATS server
Both nodes assume a NATS server is reachable at nats://localhost:4222. Start one however you prefer —
for example with Docker:
docker run -p 4222:4222 nats
Then bring up both nodes (order does not matter — each side can wait for the other's services before it calls). For more transporter options (Redis, MQTT, Kafka, AMQP, JMS, TCP) and their cross-language compatibility, see Transporters; for serializer options see Serializers.
# The alignment rules at a glance
| Knob | Node.js | Java | Why |
|---|---|---|---|
| Transporter | transporter: "nats://localhost:4222" | new NatsTransporter("nats://localhost:4222") | Both nodes must share one bus. |
| Serializer | serializer: "JSON" | nats.setSerializer(new JsonSerializer()) | JSON is the wire-compatible default on both. |
| Protocol | v5 (Moleculer 0.15, default) | cfg.setProtocolVersion("5") (default) | A node silently drops packets with a different version. |
namespace | namespace: "..." (default "") | cfg.setNamespace("...") (default "") | Part of the channel names; nodes in different namespaces never meet. |
nodeID | nodeID: "node-node" | cfg.setNodeID("java-node") | Must be unique; colliding ids break discovery. |
Talking to a legacy Moleculer JS 0.14 node? Set the Java side to
cfg.setProtocolVersion("4")— v4 and v5 are wire-compatible for the JSON serializer.
Next: try it end to end in the Quick start.