Skip to main content

Sender Propagation

Overview​

The actor system now supports consistent sender context propagation through actor hierarchies, making it work seamlessly with the ask pattern.

Two Approaches​

1. Using tell() - No Sender Propagation (Default)​

When you use tell(), the sender context is NOT propagated:

// Grandparent asks Parent
Pid response = grandparent.ask(parentPid, new Request());

// Inside Parent's receive method:
@Override
public void receive(Message msg, ActorContext context) {
// Sender is available via context

// Send to child using tell - sender context is lost
childPid.tell(new ChildMessage());

// Inside Child's receive method, no sender context available
}

2. Using forward() - Preserves Original Sender (New!)​

When you use forward(), the original sender context is preserved:

// Grandparent asks Parent
Pid response = grandparent.ask(parentPid, new Request());

// Inside Parent's receive method:
@Override
public void receive(Message msg, ActorContext context) {
// Sender context available

// Forward to child - preserves grandparent as sender
context.forward(childPid, new ChildMessage());

// Inside Child's receive method:
// Sender context preserved - child can reply to grandparent
}

Use Cases​

Use Case 1: Request Forwarding​

When an actor acts as a router/proxy and wants the final handler to reply to the original requester:

public class RouterHandler implements Handler<Message> {
@Override
public void receive(Message msg, ActorContext context) {
if (msg instanceof RoutableRequest req) {
Pid handler = selectHandler(req);

// Forward preserves original sender for reply
context.forward(handler, req);
}
}
}

public class HandlerActor implements Handler<Message> {
@Override
public void receive(Message msg, ActorContext context) {
if (msg instanceof RoutableRequest req) {
// Process request
Response response = process(req);

// Reply directly to original requester, not router
context.reply(response);
}
}
}

Use Case 2: Multi-Level Processing Pipeline​

// Grandparent initiates request
Pid result = grandparent.ask(parentPid, new ProcessRequest());

// Parent forwards to child
public class ParentHandler implements Handler<Message> {
@Override
public void receive(Message msg, ActorContext context) {
if (msg instanceof ProcessRequest req) {
// Do some preprocessing
ProcessRequest enhanced = preprocess(req);

// Forward to child - grandparent remains the sender
context.forward(childPid, enhanced);
}
}
}

// Child processes and replies to grandparent
public class ChildHandler implements Handler<Message> {
@Override
public void receive(Message msg, ActorContext context) {
if (msg instanceof ProcessRequest req) {
ProcessResult result = process(req);

// Reply goes to grandparent (original requester)
context.reply(result);
}
}
}

Use Case 3: Handler-Based API​

The forward() method is also available in the handler-based API through ActorContext:

public class MyHandler implements Handler<Message> {
@Override
public void receive(Message msg, ActorContext context) {
if (msg instanceof ForwardableRequest req) {
Pid nextActor = selectNextActor(req);

// Forward preserves sender context
context.forward(nextActor, req);
}
}
}

When to Use Each​

MethodUse WhenSender Context
tell()Normal message passing, no reply expectedLost (Optional.empty())
forward()Acting as intermediary, want final actor to reply to original senderPreserved
ask()Request-response pattern, you are the requesterYou become the sender

Implementation Details​

  • Sender context is stored in a ThreadLocal variable
  • forward() wraps the message with MessageWithSender to preserve context
  • Context is automatically cleared after message processing
  • Works seamlessly with the ask pattern
  • Compatible with both traditional Actor subclassing and Handler-based API

Best Practices​

  1. Use forward() for request routing: When your actor is a proxy/router
  2. Use tell() for fire-and-forget: When no reply is needed
  3. Use ask() for request-response: When you need a reply
  4. Use Optional methods: getSender() returns Optional<Pid> - use ifPresent(), map(), or orElse() for clean handling