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 with Pekko (an open-source fork of Akka).

Discord Documentation

Why spring-boot-starter-actor?

I'm a Java developer, and I love using the actor model. However, Spring is everywhere in production. The goal of this project is to help Spring Boot projects easily integrate actor models with a great developer experience.

Key Features:

  • Auto-configure necessary actor components (e.g., ActorSystem) within Spring Boot's context
  • Dependency injection for actors
  • Simplify cluster and sharding
  • Built-in metrics using ByteBuddy to intercept actors and collect metrics
Live Demo - Distributed Chat Application

With spring-boot-starter-actor, you can build stateful distributed systems without third-party middleware (e.g., Redis, Kafka).

Architecture

graph TB
    subgraph "Node 1"
        U1[UserActor 1]
        subgraph CR["ChatRoomActor"]
            T[Topic]
        end
    end

    subgraph "Node 2"
        U2[UserActor 2]
    end

    subgraph "Node 3"
        U3[UserActor 3]
    end

    WS1[🔌 WebSocket] --> U1
    WS2[🔌 WebSocket] --> U2
    WS3[🔌 WebSocket] --> U3

    U1 -.->|subscribe| T
    U2 -.->|subscribe| T
    U3 -.->|subscribe| T

    U1 -->|send message| CR
    T -->|broadcast| U1
    T -->|broadcast| U2
    T -->|broadcast| U3

    style CR fill:#8B5CF6,stroke:#7C3AED,color:#fff
    style T fill:#A78BFA,stroke:#8B5CF6,color:#fff
    style U1 fill:#06B6D4,stroke:#0891B2,color:#fff
    style U2 fill:#06B6D4,stroke:#0891B2,color:#fff
    style U3 fill:#06B6D4,stroke:#0891B2,color:#fff
Loading

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 static class Greet extends AskCommand<String> implements Command {
        public final String name;

        public Greet(String name) {
            this.name = name;
        }
    }

    @Override
    public SpringActorBehavior<Command> create(SpringActorContext actorContext) {
        return SpringActorBehavior.builder(Command.class, actorContext)
                .onMessage(Greet.class, (ctx, msg) -> {
                    msg.reply("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
                        .ask(new GreeterActor.Greet(name))
                        .withTimeout(Duration.ofSeconds(5))
                        .execute()
                );
    }
}

Core Concepts

Actor Lifecycle Management

Spawn a New Actor:

// Create and start a new actor
CompletionStage<SpringActorHandle<Command>> actorHandle = 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<SpringActorHandle<Command>> actorHandle = actorSystem
                .get(MyActor.class, "my-actor-1");

Get or Spawn (Recommended):

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

Check if Actor Exists:

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

Stop an Actor:

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

Communication Patterns

Fire-and-forget (tell):

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

Request-response (ask):

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

With error handling:

CompletionStage<String> response = actor
        .ask(new GetValue())
        .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://MyActorSystem@127.0.0.1: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 static class GetActivity extends AskCommand<String> implements Command {
        public GetActivity() {
        }
    }

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

    @Override
    public SpringShardedActorBehavior<Command> create(SpringShardedActorContext<Command> ctx) {
        return SpringShardedActorBehavior.builder(Command.class, ctx)
                .withState(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.reply(activity);
            return Behaviors.same();
        }
    }
}

Using Sharded Actors:

// Get reference (entity created on-demand)
SpringShardedActorHandle<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
        .ask(new GetActivity())
        .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) -> {
                    SpringActorHandle<Command> self = new SpringActorHandle<>(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<SpringActorHandle<Command>> child = parentRef
                .child(ChildActor.class)
                .withId("child-1")
                .spawn();

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

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

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

# run frontend 
$ cd example/chat/frontend
$ npm run dev 

# Stop cluster
$ sh cluster-stop.sh

Monitoring

WIP

Documentation

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

Community & Support

Join our community to ask questions, share ideas, and get help:

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

See roadmap/ROADMAP.md for the implementation roadmap.

License

This project is licensed under the Apache License 2.0.

About

Actors kindly introduced to Spring

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors