Skip to content

seonWKim/spring-boot-starter-actor

Repository files navigation

Library Logo

Spring Boot Starter Actor

Bring the power of the actor model to your Spring Boot applications using Pekko (an open-source fork of Akka).

Why spring-boot-starter-actor?

Build scalable, concurrent systems—real-time chat, IoT platforms, event processing—using the actor model with familiar Spring patterns.

Key Features:

  • Auto-configuration with Spring Boot
  • Full Spring dependency injection support
  • Local and cluster mode support
  • Built-in metrics and monitoring
Live Demo - Distributed Chat Application

Quick Start

Prerequisites

  • Java 11 or higher
  • Spring Boot 2.x or 3.x

Installation

Add the dependency to your project:

Gradle:

dependencyManagement {
    imports {
        // Pekko requires Jackson 2.17.3+
        mavenBom("com.fasterxml.jackson:jackson-bom:2.17.3")
    }
}

// Spring Boot 2.7.x
implementation 'io.github.seonwkim:spring-boot-starter-actor:0.3.0'

// Spring Boot 3.2.x
implementation 'io.github.seonwkim:spring-boot-starter-actor_3:0.3.0'

Maven:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.fasterxml.jackson</groupId>
      <artifactId>jackson-bom</artifactId>
      <version>2.17.3</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<!-- Spring Boot 2.7.x -->
<dependency>
  <groupId>io.github.seonwkim</groupId>
  <artifactId>spring-boot-starter-actor</artifactId>
  <version>0.3.0</version>
</dependency>

<!-- Spring Boot 3.2.x -->
<dependency>
  <groupId>io.github.seonwkim</groupId>
  <artifactId>spring-boot-starter-actor_3</artifactId>
  <version>0.3.0</version>
</dependency>

Latest versions: spring-boot-starter-actor | spring-boot-starter-actor_3

Enable Actor Support

Add @EnableActorSupport to your application:

@SpringBootApplication
@EnableActorSupport
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

Create Your First Actor

Create an actor by implementing SpringActor:

@Component
public class GreeterActor implements SpringActor<GreeterActor.Command> {

    public interface Command {}

    public record Greet(String name, ActorRef<String> replyTo) implements Command {}

    @Override
    public SpringActorBehavior<Command> create(SpringActorContext actorContext) {
        return SpringActorBehavior.builder(Command.class, actorContext)
            .onMessage(Greet.class, (ctx, msg) -> {
                msg.replyTo.tell("Hello, " + msg.name + "!");
                return Behaviors.same();
            })
            .build();
    }
}

Use Actors in Your Services

Inject SpringActorSystem and interact with actors:

@Service
public class GreeterService {
    private final SpringActorSystem actorSystem;

    public GreeterService(SpringActorSystem actorSystem) {
        this.actorSystem = actorSystem;
    }

    public CompletionStage<String> greet(String name) {
        return actorSystem.getOrSpawn(GreeterActor.class, "greeter")
            .thenCompose(actor -> actor
                .askBuilder(replyTo -> new GreeterActor.Greet(name, replyTo))
                .withTimeout(Duration.ofSeconds(5))
                .execute()
            );
    }
}

Core Concepts

Actor Lifecycle Management

Spawn a New Actor:

// Create and start a new actor
CompletionStage<SpringActorRef<Command>> actorRef = actorSystem
    .actor(MyActor.class)
    .withId("my-actor-1")
    .withTimeout(Duration.ofSeconds(5))  // Optional
    .spawn();

Get Existing Actor:

// Get reference to existing actor (returns null if not found)
CompletionStage<SpringActorRef<Command>> actorRef = actorSystem
    .get(MyActor.class, "my-actor-1");

Get or Spawn (Recommended):

// Automatically gets existing or spawns new actor
CompletionStage<SpringActorRef<Command>> actorRef = actorSystem
    .getOrSpawn(MyActor.class, "my-actor-1");

Check if Actor Exists:

CompletionStage<Boolean> exists = actorSystem
    .exists(MyActor.class, "my-actor-1");

Stop an Actor:

actorRef.thenAccept(actor -> actor.stop());

Communication Patterns

Fire-and-forget (tell):

actor.tell(new ProcessOrder("order-123"));

Request-response (ask):

CompletionStage<String> response = actor
    .askBuilder(GetValue::new)
    .withTimeout(Duration.ofSeconds(5))
    .execute();

With error handling:

CompletionStage<String> response = actor
    .askBuilder(GetValue::new)
    .withTimeout(Duration.ofSeconds(5))
    .onTimeout(() -> "default-value")
    .execute();

Spring Dependency Injection

Actors are Spring components with full DI support:

@Component
public class OrderActor implements SpringActor<OrderActor.Command> {

    private final OrderRepository orderRepository;

    public OrderActor(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public interface Command {}
    public record ProcessOrder(String orderId) implements Command {}

    @Override
    public SpringActorBehavior<Command> create(SpringActorContext actorContext) {
        return SpringActorBehavior.builder(Command.class, actorContext)
            .onMessage(ProcessOrder.class, (ctx, msg) -> {
                Order order = orderRepository.findById(msg.orderId);
                // Process order...
                return Behaviors.same();
            })
            .build();
    }
}

Configuration

Local Mode (Default)

spring:
  actor:
    pekko:
      actor:
        provider: local

Cluster Mode

spring:
  actor:
    pekko:
      actor:
        provider: cluster
      remote:
        artery:
          canonical:
            hostname: "127.0.0.1"
            port: 2551
      cluster:
        seed-nodes:
          - "pekko://[email protected]:2551"

Advanced Features

Sharded Actors (Cluster Mode)

For distributed systems, use sharded actors that are automatically distributed across cluster nodes:

Define a Sharded Actor:

@Component
public class UserSessionActor implements SpringShardedActor<UserSessionActor.Command> {

    public static final EntityTypeKey<Command> TYPE_KEY =
        EntityTypeKey.create(Command.class, "UserSession");

    public interface Command extends JsonSerializable {}

    public record UpdateActivity(String activity) implements Command {}
    public record GetActivity(ActorRef<String> replyTo) implements Command {}

    @Override
    public EntityTypeKey<Command> typeKey() {
        return TYPE_KEY;
    }

    @Override
    public SpringShardedActorBehavior<Command> create(EntityContext<Command> ctx) {
        return SpringShardedActorBehavior.builder(Command.class, ctx)
            .onCreate(entityCtx -> new UserSessionBehavior(ctx.getEntityId()))
            .onMessage(UpdateActivity.class, UserSessionBehavior::onUpdateActivity)
            .onMessage(GetActivity.class, UserSessionBehavior::onGetActivity)
            .build();
    }

    private static class UserSessionBehavior {
        private final String userId;
        private String activity = "idle";

        UserSessionBehavior(String userId) {
            this.userId = userId;
        }

        Behavior<Command> onUpdateActivity(UpdateActivity msg) {
            this.activity = msg.activity;
            return Behaviors.same();
        }

        Behavior<Command> onGetActivity(GetActivity msg) {
            msg.replyTo.tell(activity);
            return Behaviors.same();
        }
    }
}

Using Sharded Actors:

// Get reference (entity created on-demand)
SpringShardedActorRef<Command> actor = actorSystem
    .sharded(UserSessionActor.class)
    .withId("user-123")
    .get();

// Fire-and-forget
actor.tell(new UpdateActivity("logged-in"));

// Request-response
CompletionStage<String> activity = actor
    .askBuilder(GetActivity::new)
    .withTimeout(Duration.ofSeconds(5))
    .execute();

Key Differences from Regular Actors:

  • Created automatically when first message arrives (no spawn() needed)
  • Always available, even if not currently running
  • Automatically distributed across cluster nodes
  • Passivated after idle timeout (configurable)
  • Use get() to obtain reference (not spawn())

Supervision and Fault Tolerance

Build self-healing systems with supervision strategies:

Available Strategies:

// Restart on failure (default)
SupervisorStrategy.restart()

// Restart with limit (e.g., 3 times within 1 minute)
SupervisorStrategy.restart().withLimit(3, Duration.ofMinutes(1))

// Stop on failure
SupervisorStrategy.stop()

// Resume and ignore failure
SupervisorStrategy.resume()

Spawn Actors with Supervision:

// Top-level actor
actorSystem.actor(WorkerActor.class)
    .withId("worker-1")
    .withSupervisionStrategy(SupervisorStrategy.restart().withLimit(3, Duration.ofMinutes(1)))
    .spawn();

Spawn Child Actors with Supervision:

@Component
public class SupervisorActor implements SpringActor<SupervisorActor.Command> {

    public interface Command {}

    @Override
    public SpringActorBehavior<Command> create(SpringActorContext actorContext) {
        return SpringActorBehavior.builder(Command.class, actorContext)
            .onMessage(DelegateWork.class, (ctx, msg) -> {
                SpringActorRef<Command> self = new SpringActorRef<>(ctx.getSystem().scheduler(), ctx.getSelf());

                // Spawn supervised child
                self.child(WorkerActor.class)
                    .withId("worker-1")
                    .withSupervisionStrategy(SupervisorStrategy.restart())
                    .spawn();

                return Behaviors.same();
            })
            .build();
    }
}

Child Actor Operations:

// Spawn new child
CompletionStage<SpringActorRef<Command>> child = parentRef
    .child(ChildActor.class)
    .withId("child-1")
    .spawn();

// Get existing child
CompletionStage<SpringActorRef<Command>> existing = parentRef
    .child(ChildActor.class)
    .withId("child-1")
    .get();

// Get or spawn (recommended)
CompletionStage<SpringActorRef<Command>> childRef = parentRef
    .child(ChildActor.class)
    .withId("child-1")
    .getOrSpawn();

Interactive Demo:

Supervision Interactive Demo - Actor Hierarchy Visualization

Learn more in the Supervision Guide.

Running Examples

Chat Application (Distributed)

Run a distributed chat application across multiple nodes:

# Start 3-node cluster on ports 8080, 8081, 8082
$ sh cluster-start.sh chat io.github.seonwkim.example.SpringPekkoApplication 8080 2551 3

# Stop cluster
$ sh cluster-stop.sh

Or use Docker:

cd example/chat
sh init-local-docker.sh

# Access at http://localhost:8080, 8081, 8082
# View logs: docker-compose logs -f chat-app-0
# Stop: docker-compose down

Monitoring

Built-in Prometheus metrics and Grafana dashboards:

cd scripts/monitoring
docker-compose up -d

Access:

Documentation

Full documentation: https://seonwkim.github.io/spring-boot-starter-actor/

Contributing

Contributions welcome! Please:

  1. Create an issue describing your contribution
  2. Open a PR with clear explanation
  3. Run ./gradlew spotlessApply for formatting
  4. Ensure tests pass

License

This project is licensed under the Apache License 2.0.

About

Actors kindly introduced to Spring

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •