# Data types & features across the wire

This is the reference for what crosses the Java ↔ Node.js boundary intact, and how each capability looks in both languages. Every example is trimmed from a runnable, test-verified integration demo, so the shapes shown here are exactly what survives the round trip.

On the wire there is just JSON, so the rule is simple: stick to JSON-safe values and everything — primitives, arrays, nested objects, events, cached results, metadata — round-trips identically. Binary data travels on a separate streaming channel.

On the Java side these values are built and read through the io.datatree.Tree API, the equivalent of a JavaScript object (see the DataTree API).

AVOID NON-JSON TYPES

Do not put language-specific values — JavaScript Date, Map, Set, BigInt, Buffer, or Java Date/byte[] — inside action params or responses. The native JSON serializer used on both sides cannot represent them identically, so they would not survive the round trip. For binary data, use Binary streaming instead of embedding bytes in JSON.

Capability Crosses intact Section
Primitives (int, large int, double, boolean, string, null) Primitives
Arrays / lists (incl. nested) Lists
Objects / maps (nested) Maps
Deeply nested, life-like structures Complex structures
Events (emit and broadcast) Events
Cached action results (with TTL) Caching
Request/response metadata Metadata
Errors & exceptions (typed, structured) Errors & exceptions
Binary data ✓ (streaming) Binary streaming

# Primitives

Every JSON-safe primitive comes back with the same type and value. Note that large integers are safe up to JavaScript's Number.MAX_SAFE_INTEGER (9007199254740991), and null is a real JSON null on both sides.

    # Lists (arrays)

    A list — including lists of objects with their own nested arrays — round-trips as a JSON array. In Java you build it with putList(...) / addMap(); a caller reads it back as an array.

      # Maps (nested objects)

      Nested keys and values survive unchanged. putMap(...) on the Java side is the equivalent of a nested JavaScript object.

        # Deep echo

        The strongest single proof that structured data crosses intact: an echo action that returns its input unchanged. A caller sends a rich nested object and gets back a deep-equal copy.

          # Complex nested structures

          A life-like object — a list of users, each with a nested address (holding a geo sub-object), arrays of strings (emails, roles) and an array of objects (phones) — crosses with full deep equality. This is also the clearest side-by-side of the Tree API and a JavaScript object (one user shown; the demo sends two).

            # Events

            Events cross the language boundary too, and the two delivery modes behave the same way across languages:

            • emit is load-balanced — one listener per group receives the event.
            • broadcast is delivered to every listener on every node.

            A service subscribes with an event listener; any node sends with emit or broadcast. (For listener groups, see Events.)

            Subscribing:

              Sending:

                # Caching with TTL

                Caching happens on the node that owns the action, so a remote caller in the other language benefits from the cache too. The result is cached by the listed key(s) for ttl seconds: two calls with the same key within the TTL return the cached value (the body does not run again), a different key is a different cache entry, and after the TTL the entry is evicted and the body runs again. (See Caching for cacher options.)

                  # Metadata

                  Metadata is the one wire field with two APIs. It rides alongside the params and is merged back into the caller with the response — but you reach it differently in each language:

                  • Node.js: ctx.meta
                  • Java: ctx.params.getMeta()

                  Both serialize to the request's top-level meta field, so they are fully interchangeable. Keep meta values JSON-safe.

                  Reading and answering metadata in a service:

                    Sending metadata on a call, and reading what came back:

                      The most common real use is one-directional: a caller passes cross-cutting context (tenant, auth token, locale) in meta — never in params — and the remote service only reads it. Here a Node.js caller scopes a Java service by tenant:

                        # Errors & exceptions

                        An error is not just a string on the wire — it crosses as a structured object, so the calling side in the other language gets a typed exception with the same fields, not a flattened message. When a Java action throws (or a Promise rejects), moleculer-java serializes the error into the response packet; the Node.js caller receives a Moleculer error with the matching properties, and vice versa.

                        These are the fields that travel (the Java classes live in services.moleculer.error):

                        Wire field Node.js (err.…) Java (MoleculerError) Meaning
                        name err.name getName() The error class — the cross-language discriminator used to rebuild the right type.
                        message err.message getMessage() Human-readable message.
                        code err.code getCode() HTTP-like status code (e.g. 422, 500).
                        type err.type getType() Machine-readable type string you choose (e.g. "TENANT_MISSING").
                        data err.data getData()Tree Arbitrary JSON detail payload.
                        retryable err.retryable isRetryable() Whether the caller's retry logic may retry the call.
                        nodeID err.nodeID originating node id Which node produced the error.
                        stack err.stack getStack() Stack trace, as a string.

                        Java throws → Node.js catches. The Java side throws a typed error; the Node.js caller reads the same fields back:

                          Node.js throws → Java catches. A built-in Node.js Moleculer error is rebuilt as the matching Java class on arrival:

                            A few rules that are pure protocol behavior — you cannot guess them, so rely on them:

                            • name is the discriminator. moleculer-java maps a known name back to the matching Java class (MoleculerError, MoleculerRetryableError, MoleculerServerError, MoleculerClientError, ValidationError (422), ServiceNotFoundError, RequestTimeoutError, …). An unknown name (e.g. a custom Node.js error class) arrives as a generic MoleculerError that still carries all the fields above. Node.js applies the same fall-back in the other direction.
                            • A plain Java exception is auto-wrapped. If something that is not a MoleculerError escapes an action, moleculer-java wraps it as a generic MoleculerError (name "MoleculerError", code 500, type "UNKNOWN_ERROR", retryable false) before sending — the other side never receives a raw Java stack, always the structured shape.
                            • retryable drives retries. The caller's retry logic (see Fault tolerance and CallOptions.retryCount) only retries calls that failed with a retryable = true error; throw a MoleculerRetryableError (or set retryable) when a retry could succeed.

                            # Binary streaming

                            Binary data does not travel as JSON params — it travels on Moleculer's streaming channel. A streamed request opens with empty params; any side-data (such as a filename) must travel in meta, never in params, or the receiver would treat the first packet as stream content.

                            Receiving a stream (an action that consumes incoming bytes):

                              Sending a stream (the caller opens a stream and pushes bytes; note: stream only, no params):

                                Producing a stream (an action that returns bytes for the caller to download):

                                  # See also

                                  Every snippet on this page is trimmed from a runnable, test-verified integration demo where a Java node and a Node.js node exchange exactly these shapes and assert they arrive intact in both directions.