|
Bring the power of the actor model to your Spring Boot applications using Pekko (an open-source fork of Akka). |
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
- Java 11 or higher
- Spring Boot 2.x or 3.x
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
Add @EnableActorSupport to your application:
@SpringBootApplication
@EnableActorSupport
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}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();
}
}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()
);
}
}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());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();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();
}
}spring:
actor:
pekko:
actor:
provider: localspring:
actor:
pekko:
actor:
provider: cluster
remote:
artery:
canonical:
hostname: "127.0.0.1"
port: 2551
cluster:
seed-nodes:
- "pekko://[email protected]:2551"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 (notspawn())
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:
Learn more in the Supervision Guide.
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.shOr 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 downBuilt-in Prometheus metrics and Grafana dashboards:
cd scripts/monitoring
docker-compose up -dAccess:
- Prometheus: http://localhost:9090
- Grafana: http://localhost:3000 (admin/admin)
Full documentation: https://seonwkim.github.io/spring-boot-starter-actor/
Contributions welcome! Please:
- Create an issue describing your contribution
- Open a PR with clear explanation
- Run
./gradlew spotlessApplyfor formatting - Ensure tests pass
This project is licensed under the Apache License 2.0.


