A lightweight Java framework for Vertical Slice Architecture (VSA) that enables feature-complete, isolated slices with built-in dependency injection and HTTP routing.
Unlike traditional layered architecture where code is organized horizontally by technical concerns (controllers, services, repositories), Vertical Slice Architecture organizes code vertically by business features.
| Layered Architecture | Vertical Slice Architecture |
|---|---|
controllers/ → services/ → repositories/ |
create-order/ → update-order/ → cancel-order/ |
| Technical separation | Feature separation |
| Shared across features | Isolated per feature |
| High coupling between layers | Low coupling between slices |
Layered Approach:
src/
├── controllers/OrderController.java
├── services/OrderService.java
├── repositories/OrderRepository.java
└── models/Order.java
VSA Approach:
src/
├── create-order/
│ ├── CreateOrderSlice.java
│ ├── OrderDataAccess.java
│ └── OrderEmailService.java
├── update-order/
│ ├── UpdateOrderSlice.java
│ └── OrderValidator.java
└── cancel-order/
├── CancelOrderSlice.java
└── RefundService.java
Generative AI agents like Claude work exceptionally well with VSA because:
- 🎯 Focused Context: Each slice contains everything needed for one feature
- 🔍 Reduced Cognitive Load: AI can understand complete workflows in isolation
- ⚡ Faster Iteration: Changes are localized and less likely to break other features
- 🧪 Easier Testing: Each slice can be tested independently
- 📝 Self-Documenting: Business logic is co-located with implementation
SliceKit enforces VSA principles through:
- Slice Isolation: Components are scoped to their slice by default
- Explicit Sharing: Use
@SharedComponentonly when intentional - Route Ownership: Each slice owns exactly one HTTP endpoint
- Dependency Boundaries: Child injectors prevent cross-slice contamination
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>io.undertow</groupId>
<artifactId>undertow-core</artifactId>
<version>2.3.10.Final</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.0</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>7.0.0</version>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Validation Framework -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.1.Final</version>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.2</version>
</dependency>
</dependencies>@Slice(
name = "create-user",
route = "/api/users",
method = HttpMethod.POST,
description = "Creates a new user account"
)
public class CreateUserSlice {
private final UserDataAccess dataAccess;
private final EmailService emailService;
@Inject
public CreateUserSlice(UserDataAccess dataAccess, EmailService emailService) {
this.dataAccess = dataAccess;
this.emailService = emailService;
}
@SliceHandler
public CreateUserResult handle(@Valid CreateUserRequest request) {
// Complete user creation logic here
String userId = UUID.randomUUID().toString();
User user = dataAccess.saveUser(new User(userId, request.getEmail()));
emailService.sendWelcomeEmail(user.getEmail());
return new CreateUserResult(userId, "User created successfully");
}
// Request with validation annotations
public static class CreateUserRequest {
@NotBlank(message = "Email is required")
@Email(message = "Must be a valid email address")
private String email;
@NotBlank(message = "Name is required")
@Size(min = 2, max = 50, message = "Name must be between 2 and 50 characters")
private String name;
// getters and setters...
}
}@SliceComponent("create-user")
public class UserDataAccess {
public User saveUser(User user) {
// Save user logic
return user;
}
}
@SliceComponent("create-user")
public class EmailService {
public void sendWelcomeEmail(String email) {
// Send email logic
}
}public class MyApp {
public static void main(String[] args) {
SliceKit.builder()
.scanPackages("com.mycompany.slices")
.port(8080)
.start();
}
}SliceKit includes built-in Bean Validation support for automatic request validation:
@SliceHandler
public CreateOrderResult handle(@Valid CreateOrderRequest request) {
// Validation happens automatically before this method is called
// Invalid requests return HTTP 400 with detailed error messages
return processOrder(request);
}
public static class CreateOrderRequest {
@NotBlank(message = "Customer ID is required")
@Email(message = "Must be a valid email address")
private String customerId;
@NotEmpty(message = "Order must contain at least one item")
@Size(min = 1, max = 10, message = "Order can contain between 1 and 10 items")
private String[] items;
@DecimalMin(value = "0.01", message = "Order total must be at least $0.01")
private BigDecimal totalAmount;
}Invalid requests automatically return HTTP 400 with detailed validation errors:
{
"error": "Validation Failed",
"message": "customerId: Must be a valid email address; items: Order must contain at least one item; totalAmount: Order total must be at least $0.01",
"violationCount": 3
}@NotNull- Field cannot be null@NotBlank- String cannot be null, empty, or whitespace-only@NotEmpty- Collection/array cannot be null or empty@Email- String must be a valid email format@Size(min, max)- String/collection size constraints@DecimalMin/@DecimalMax- Numeric minimum/maximum values@Pattern- String must match regex pattern- And many more from Jakarta Bean Validation!
A slice is a complete vertical feature containing:
- HTTP endpoint definition (
@Slice) - Request handler (
@SliceHandler) - Request validation (
@Validwith Bean Validation) - Business logic and dependencies
- Data access and external integrations
@SliceComponent("slice-name"): Scoped to a specific slice@SharedComponent: Available to multiple slices (use sparingly)@Inject: Constructor dependency injection
// ✅ Good: Slice-specific component
@SliceComponent("create-order")
public class OrderDataAccess { }
// ⚠️ Use carefully: Shared across slices
@SharedComponent(sharedWith = {"create-order", "update-order"})
public class OrderValidator { }
// ❌ Avoid: Global shared component
@SharedComponent
public class GlobalService { }SliceKit includes working examples in src/main/java/io/slicekit/examples/:
- HelloWorldSlice - Basic GET endpoint
- HealthCheckSlice - System health monitoring
- CreateOrderSlice - POST endpoint with dependency injection
- OrderDataAccess - Slice-scoped data access
- OrderEmailService - Slice-scoped email service
- EchoSlice - POST endpoint with JSON request/response
# Compile the project
mvn clean compile
# Start the example application
mvn exec:java -Dexec.mainClass="io.slicekit.examples.SliceKitExampleApp"
# Test the endpoints
curl http://localhost:8080/hello
curl http://localhost:8080/health
# Test successful order creation
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"customerId":"test@example.com","items":["item1"],"totalAmount":29.99}'
# Test validation errors
curl -X POST http://localhost:8080/api/orders \
-H "Content-Type: application/json" \
-d '{"customerId":"invalid-email","items":[],"totalAmount":-5.00}'- Parallel Development: Teams can work on different slices simultaneously
- Easier Onboarding: New developers can understand one slice at a time
- Reduced Merge Conflicts: Changes are isolated to specific slices
- Clear Ownership: Each slice has a clear business purpose
- Complete Context: AI can see entire workflow in one place
- Predictable Patterns: Consistent slice structure across features
- Isolated Changes: Modifications don't cascade across the system
- Self-Contained Testing: Each slice can be tested independently
- Independent Deployment: Slices can potentially be deployed separately
- Easier Debugging: Issues are contained within slice boundaries
- Simplified Testing: Unit and integration tests are slice-focused
- Clear Dependencies: Explicit component scoping and sharing
SliceKit enforces VSA principles through:
- Automatic Slice Discovery: Scans for
@Sliceannotated classes - Route Conflict Detection: Prevents duplicate route registrations
- Dependency Isolation: Components are slice-scoped by default
- Explicit Sharing:
@SharedComponentrequires intentional declaration - Constructor Injection: Enforces dependency declaration at construction time
- Keep slices focused on single business capabilities
- Use descriptive slice and component names
- Minimize use of
@SharedComponent - Write integration tests per slice
- Co-locate related functionality within slices
- Create god slices that handle multiple concerns
- Share mutable state between slices
- Use static dependencies or singletons
- Bypass the injection system with direct instantiation
- Create deep inheritance hierarchies across slices
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Java 17+
- Maven 3.6+
- Undertow - Embedded HTTP server
- Jackson - JSON serialization/deserialization
- Google Guice - Dependency injection
- Hibernate Validator - Bean Validation (JSR-380) implementation
- Reflections - Classpath scanning
- SLF4J + Logback - Logging
This project is licensed under the MIT License - see the LICENSE file for details.
- 📖 Documentation: Check the examples in
src/main/java/io/slicekit/examples/ - 🐛 Issues: Report bugs and feature requests via GitHub Issues
- 💬 Discussions: Use GitHub Discussions for questions and ideas
SliceKit - Building features, not layers 🎯