Event Envelope Reference

Reference: Complete API reference for EventEnvelope and PostOffice.

EventEnvelope is the universal message container in Mercury Composable. Every event that travels through the event bus — whether triggered by an HTTP request, a flow step, or a direct PostOffice call — is carried inside an EventEnvelope. Understanding its structure is prerequisite for advanced function patterns, error handling, and distributed tracing.

Package: org.platformlambda.core.models


Structure overview

An envelope has three distinct, independent parts:

Part Java type Purpose
Body Object (PoJo, Map<String,Object>, Java primitive, byte[]) The event payload. Serialized as MsgPack on the event bus; JSON at HTTP boundaries.
Headers Map<String, String> User-defined key-value parameters passed directly to handleEvent. Keys and values are always strings.
Metadata Various fields Routing address, trace ID, correlation ID, status code, execution timing, exception state. Managed by the framework; user code reads but rarely writes.

The framework serializes and deserializes the body automatically. User code never calls MsgPack directly.


Creating an EventEnvelope

Constructors

// Default constructor — assigns a random UUID as the envelope ID
EventEnvelope event = new EventEnvelope();

// Deserialize from a previously serialized byte array
EventEnvelope event = new EventEnvelope(byte[] bytes);

// Reconstruct from a map representation (e.g. from toMap())
EventEnvelope event = new EventEnvelope(Map<String, Object> map);

Static factories

// Equivalent to new EventEnvelope()
EventEnvelope event = EventEnvelope.of();

// Equivalent to new EventEnvelope(byte[])
EventEnvelope event = EventEnvelope.of(byte[] bytes);

// Equivalent to new EventEnvelope(Map)
EventEnvelope event = EventEnvelope.of(Map<String, Object> map);

Builder pattern

All set* methods return this, enabling fluent construction:

EventEnvelope event = new EventEnvelope()
    .setTo("v1.get.profile")
    .setHeader("profile_id", "100")
    .setHeader("tenant", "acme")
    .setBody(requestMap)
    .setCorrelationId("req-abc-123");

Body methods

The body is the event payload. The framework maps it to the typed input argument of handleEvent and wraps the return value into the response envelope automatically.

Method Return type Description
getBody() Object Returns the deserialized body. Type depends on what the sender set. Typically Map<String,Object> or a PoJo.
getBody(Class<T> toValueType) T Deserializes the body to the given PoJo class using the framework's registered serializer.
getBody(Class<T> toValueType, Class<?>... parameterClass) T Deserializes a generic type (e.g. List<Profile>). Pass the container class and the element class(es).
getBodyAsListOfPoJo(Class<T> toValueType) List<T> Convenience method to deserialize a body that is a JSON array into a typed list.
getRawBody() Object Returns the body before any deserialization. May be a byte[], Map, or primitive depending on transport.
getOriginalBody() Object Returns the body exactly as set by the sender, without any intermediate serialization. Useful for debugging.
setBody(Object body) EventEnvelope Sets the body. Accepts any PoJo, Map<String,Object>, Java primitive, or byte[]. Returns this.

Declaring EventEnvelope as the input type

To access the full envelope (including metadata) inside a function, declare EventEnvelope as the input type in the TypedLambdaFunction signature:

@PreLoad(route = "v1.inspect.event")
public class InspectEvent implements TypedLambdaFunction<EventEnvelope, Map<String, Object>> {

    @Override
    public Map<String, Object> handleEvent(Map<String, String> headers,
                                           EventEnvelope input, int instance) throws Exception {
        String traceId = input.getTraceId();
        String correlationId = input.getCorrelationId();
        Object body = input.getBody();
        return Map.of("trace", traceId, "body", body);
    }
}

This pattern is also required when a function is annotated with @EventInterceptor.


Header methods

Headers are String → String parameters attached to an event. They are delivered directly to the headers argument of handleEvent — they are not HTTP headers unless the function is a REST endpoint handler.

Method Return type Description
getHeaders() Map<String, String> Returns all user-defined headers as an immutable map.
getHeader(String key) String Returns the value for a single header key, or null if absent.
setHeader(String key, Object value) EventEnvelope Sets a header. The value is converted to String via toString(). Returns this.
setHeaders(Map<String, String> headers) EventEnvelope Replaces all headers with the provided map. Returns this.

Reserved internal header names

The framework injects the following keys into the headers Map parameter of handleEvent. These keys are read-only context — do not set them on outgoing envelopes:

Key Value
my_route Route name of the currently executing function
my_trace_id Distributed trace ID for this transaction
my_trace_path Accumulated trace path through the call chain

Access these inside a function to preserve the trace when constructing a PostOffice:

PostOffice po = PostOffice.trackable(headers, instance);

Routing and metadata methods

Method Return type Description
getId() String The envelope's unique ID. Assigned automatically on construction (UUID).
setId(String id) EventEnvelope Override the auto-assigned ID. Rarely needed. Returns this.
getTo() String The destination route. Set by the sender or by the flow engine.
setTo(String to) EventEnvelope Set the destination route for this event. Returns this.
getFrom() String The originating route. Populated automatically by PostOffice.
setFrom(String from) EventEnvelope Overrides the sender identity. Returns this.
getReplyTo() String The route to which the response should be sent (used in async patterns).
setReplyTo(String replyTo) EventEnvelope Sets the reply-to route. Returns this.
getTraceId() String The distributed trace ID. Propagated across all calls in a transaction.
setTraceId(String traceId) EventEnvelope Sets the trace ID. Usually set by the framework. Returns this.
getTracePath() String The accumulated path of all routes this event has passed through.
setTracePath(String tracePath) EventEnvelope Sets the trace path. Returns this.
setTrace(String traceId, String tracePath) EventEnvelope Sets both trace ID and trace path in one call. Returns this.
getCorrelationId() String A caller-assigned correlation ID for matching responses to requests.
setCorrelationId(String cid) EventEnvelope Sets the correlation ID. Returns this.

Fields set automatically by PostOffice

When an event is sent via PostOffice, the following fields are populated automatically from the sending function's context. User code should not override them:

Field Source
from Current function's route (from my_route header)
traceId Current transaction's trace ID (from my_trace_id header)
tracePath Current trace path (from my_trace_path header)

Status and error methods

Envelope status codes follow the HTTP convention: 200 means success; 400–599 means error. The AppException class maps Java exceptions to HTTP-compatible status codes.

Method Return type Description
getStatus() int HTTP-compatible status code. Default is 200.
setStatus(int status) EventEnvelope Override the status code. Returns this.
hasError() boolean Returns true if getStatus() >= 400.
getError() Object Returns the error payload. When set by setException(), this is the exception message string.
getException() Throwable Returns the attached exception, or null if none.
setException(Throwable ex) EventEnvelope Attaches an exception and sets the status code from it. Returns this.
isException() boolean Returns true if a stack trace is present in the envelope.
getStackTrace() String Returns the stack trace string, or null if none.
setStackTrace(String trace) EventEnvelope Sets the stack trace string. Reserved for test use only. Returns this.

AppException status mapping

When setException(Throwable ex) is called, the framework determines the status code:

Exception type Resulting status
AppException appException.getStatus() (the value you passed to the constructor)
IllegalArgumentException 400
Any other Throwable 500

Throw AppException from user functions to return structured error responses:

throw new AppException(404, "Profile not found");
throw new AppException(422, "Invalid input: email is required");
throw new AppException(403, "Access denied", originalException);

Error detection pattern

EventEnvelope response = po.request(event, 5000).get();
if (response.hasError()) {
    throw new AppException(response.getStatus(),
        response.getError() != null ? response.getError().toString() : "Unknown error");
}
Profile profile = response.getBody(Profile.class);

Tags

Tags are String → String metadata attached to an event for internal tracking and routing decisions. They are distinct from user headers: tags are not delivered to handleEvent.

Method Return type Description
getTags() Map<String, String> Returns all tags.
setTags(Map<String, String> tags) EventEnvelope Replaces all tags. Returns this.
addTag(String key) EventEnvelope Adds a tag with an empty string value. Returns this.
addTag(String key, Object value) EventEnvelope Adds a tag. Value is converted to String. Returns this.
removeTag(String key) EventEnvelope Removes a tag by key. Returns this.
getTag(String key) String Returns the value of a single tag, or null if absent.

Annotations

Annotations carry arbitrary Object values for advanced routing and framework use cases. They are not serialized over the event bus and are not delivered to user functions.

Method Return type Description
getAnnotations() Map<String, Object> Returns all annotations.
setAnnotations(Map<String, Object> annotations) EventEnvelope Replaces all annotations. Returns this.
clearAnnotations() EventEnvelope Removes all annotations. Returns this.

Type and serialization methods

These methods expose the body's type information and support low-level serialization. User code rarely needs them directly; they are primarily used by the framework and adapters.

Method Return type Description
getType() String Fully qualified class name of the body object.
getSimpleType() String Simple class name of the body object (no package).
setType(String type) EventEnvelope Override the recorded type. Returns this.
isBinary() boolean Returns true if the body is a raw byte[].
setBinary(boolean binary) EventEnvelope Sets the binary flag. Returns this.
isOptional() boolean Returns true if the body is null and the envelope is an intentional empty response.
toBytes() byte[] Serializes the entire envelope to MsgPack bytes for transport.
load(byte[] bytes) void Populates this envelope from a previously serialized byte array in place.
toMap() Map<String, Object> Converts the envelope to a plain Java map. Useful for logging or testing.
fromMap(Map<String, Object> map) void Populates this envelope from a map representation in place.

Performance metadata (read-only)

These fields are populated by the framework after an event is processed. User code should read them for observability but must not set them.

Method Return type Description
getExecutionTime() float Milliseconds spent executing the target function. Set by the framework after processing.
getRoundTrip() float Total round-trip time in milliseconds from send to response. Set by the framework.
getBroadcastLevel() int Broadcast depth level. Used internally for broadcast routing.

Utility

Method Return type Description
copy() EventEnvelope Creates a deep copy of this envelope, including all headers, body, and metadata. Useful when you need to modify an envelope without affecting the original.

Relationship to Event Script data mapping

Event Script flows operate on envelopes internally, but the data mapping DSL exposes only specific namespaces. Understanding the mapping between envelope fields and DSL namespaces helps when debugging flows.

DSL namespace Envelope source
input.body Incoming request envelope body
input.header.<name> Incoming request HTTP header (HTTP flows)
input.path_parameter.<name> URL path parameter extracted by REST automation
input.query.<name> URL query parameter
input.method HTTP method
input.uri Request URI
input.session Session key-values from the authentication function
result The entire return value of the most recently completed task
result.<key> A specific field from the task's return value
model.<key> Per-transaction state machine (readable and writable across all tasks)
output.body Sets the response envelope body
output.header.<name> Sets a response header
output.status Sets the HTTP response status code
header.<name> Sets a key-value in the next function's headers argument
error.status Status code when an exception handler task is invoked
error.message Error message when an exception handler task is invoked
error.task Route of the task that threw the exception
error.stack Stack trace when an exception handler task is invoked

For the complete data mapping syntax and all literal value constructors (text(), int(), map(), etc.), see the Flow Configuration Schema.


PostOffice quick reference

PostOffice is the messaging client. Always obtain it inside handleEvent to preserve the distributed trace:

PostOffice po = PostOffice.trackable(headers, instance);

Constructors and static factories

// Production constructors
PostOffice(Map<String, String> headers, int instance)
PostOffice(Map<String, String> headers, int instance, CustomSerializer serializer)

// Static factories (preferred)
PostOffice.trackable(Map<String, String> headers, int instance)
PostOffice.withSerializer(Map<String, String> headers, int instance, CustomSerializer serializer)

// Test constructors (no real function context available in unit tests)
PostOffice.trackable(String myRoute, String myTraceId, String myTracePath)
PostOffice.withSerializer(String myRoute, String myTraceId, String myTracePath, CustomSerializer serializer)

Fire-and-forget

// Send with headers only (no body)
po.send(String to, Kv... parameters)

// Send with body only
po.send(String to, Object body)

// Send with body and headers
po.send(String to, Object body, Kv... parameters)

// Send a pre-built envelope
po.send(EventEnvelope event)

// Schedule delivery at a future time; returns a cancellation token
String token = po.sendLater(EventEnvelope event, Date future)

Broadcast

Delivers to all registered instances of the target function:

po.broadcast(String to, Kv... parameters)
po.broadcast(String to, Object body)
po.broadcast(String to, Object body, Kv... parameters)
po.broadcast(EventEnvelope event)

Request-Reply: three Future types

Choose the Future type based on your execution context:

Method Return type Use when
request() java.util.concurrent.Future<EventEnvelope> Inside virtual threads — .get() suspends non-destructively
asyncRequest() io.vertx.core.Future<EventEnvelope> Inside reactive or Vert.x code
eRequest() java.util.concurrent.CompletableFuture<EventEnvelope> When composing with CompletableFuture chains
// Virtual thread — sequential, readable, non-blocking
EventEnvelope response = po.request(event, 5000).get();

// Async Vert.x future
po.asyncRequest(event, 5000).onSuccess(response -> { ... });

// CompletableFuture
po.eRequest(event, 5000).thenAccept(response -> { ... });

Parallel requests (fork-n-join)

List<EventEnvelope> requests = List.of(eventA, eventB, eventC);
List<EventEnvelope> responses = po.request(requests, 5000).get();
// responses[i] corresponds to requests[i]

Event-over-HTTP (cross-instance)

// Route to a function in a peer application instance
EventEnvelope response = po.request(event, 5000,
    Map.of("X-App-Instance", "peer-instance-id"),
    "http://peer-service/api/event",
    true  // true = RPC, false = fire-and-forget
).get();

Service discovery and trace utilities

// Check if a function is available (local or mesh)
boolean available = po.exists("v1.some.service");

// Add custom data to the distributed trace
po.annotateTrace("user_id", "A12345");
po.annotateTrace("request_context", Map.of("tenant", "acme"));

// Access current trace info
String traceId = po.getTraceId();
String tracePath = po.getTracePath();

Kv helper class

Kv is a typed key-value pair for passing headers in PostOffice convenience methods:

// Package: org.platformlambda.core.models
Kv kv = new Kv(String key, Object value);  // value is converted to String

// Usage
po.send("v1.notify.user", new Kv("user_id", "100"), new Kv("action", "welcome"));

Common usage patterns

Pattern 1: Simple RPC call from inside a function

@PreLoad(route = "v1.create.profile")
public class CreateProfile implements TypedLambdaFunction<Map<String, Object>, Map<String, Object>> {

    @Override
    public Map<String, Object> handleEvent(Map<String, String> headers,
                                           Map<String, Object> input, int instance) throws Exception {
        PostOffice po = PostOffice.trackable(headers, instance);

        EventEnvelope request = new EventEnvelope()
            .setTo("v1.validate.input")
            .setBody(input);

        EventEnvelope response = po.request(request, 5000).get();

        if (response.hasError()) {
            throw new AppException(response.getStatus(), response.getError().toString());
        }

        return response.getBody(Map.class);
    }
}

Pattern 2: Parallel requests

PostOffice po = PostOffice.trackable(headers, instance);

EventEnvelope reqA = new EventEnvelope().setTo("v1.lookup.user").setHeader("id", userId);
EventEnvelope reqB = new EventEnvelope().setTo("v1.lookup.account").setHeader("id", accountId);

List<EventEnvelope> responses = po.request(List.of(reqA, reqB), 5000).get();
Map<String, Object> user = responses.get(0).getBody(Map.class);
Map<String, Object> account = responses.get(1).getBody(Map.class);

Pattern 3: Authentication function returning session variables

An authentication function can attach session variables to the approved request by returning an EventEnvelope with body = true and additional headers:

@PreLoad(route = "v1.auth.validator")
public class AuthValidator implements TypedLambdaFunction<AsyncHttpRequest, EventEnvelope> {

    @Override
    public EventEnvelope handleEvent(Map<String, String> headers,
                                     AsyncHttpRequest input, int instance) throws Exception {
        String token = input.getHeader("Authorization");
        if (!validateToken(token)) {
            throw new AppException(401, "Invalid token");
        }
        String userId = extractUserId(token);
        // Approve and inject session variable accessible as input.session.user_id in flows
        return new EventEnvelope()
            .setHeader("user_id", userId)
            .setBody(true);
    }
}

Pattern 4: Inspecting envelope metadata in an EventInterceptor

@PreLoad(route = "v1.audit.interceptor")
@EventInterceptor
public class AuditInterceptor implements TypedLambdaFunction<EventEnvelope, Void> {

    @Override
    public Void handleEvent(Map<String, String> headers,
                            EventEnvelope input, int instance) throws Exception {
        log.info("Event from={} to={} traceId={} bodyType={}",
            input.getFrom(), input.getTo(),
            input.getTraceId(), input.getSimpleType());
        return null;  // return value is ignored for @EventInterceptor
    }
}

Reserved HTTP headers

These header names are reserved by the framework and must not be used as custom headers in user code when operating at the HTTP boundary:

Header Purpose
X-Stream-Id Stream ID for Flux streaming responses
X-TTL Time-to-live for Flux stream consumers
X-Trace-Id Distributed trace identifier
X-Correlation-Id Caller-assigned correlation ID
X-Flow-Id Active flow ID
X-App-Instance Target application instance for Event-over-HTTP
X-Async Marks an event as fire-and-forget over HTTP
X-Event-Api Marks a request as an Event API call
X-Content-Length Content length for streaming responses
X-Small-Payload-As-Bytes Hint to treat small payloads as binary
X-Raw-Xml Hint to pass XML body as-is without parsing

See also