Build, test, and deploy your first slice from scratch.
| Tool | Version | Check |
|---|---|---|
| Java | 25+ | java --version |
| Maven | 3.8+ | mvn --version |
| JBCT CLI | 1.0.0-rc1+ | jbct --version |
All JBCT and Aether artifacts are published to Maven Central. The generated POM references them automatically — no additional repository configuration needed.
jbct init my-first-slice --slice
cd my-first-slice
This generates a complete Aether slice project. You can customize coordinates:
jbct init my-first-slice -g org.mycompany -a my-first-slice --slice
Generated directory tree:
my-first-slice/
├── pom.xml # Maven build (Java 25, JBCT plugin)
├── jbct.toml # JBCT formatter/linter config
├── forge.toml # Forge cluster configuration
├── aether.toml # Runtime resource configuration
├── run-forge.sh # Start local Forge server
├── start-postgres.sh # Start PostgreSQL container
├── stop-postgres.sh # Stop PostgreSQL container
├── deploy-forge.sh # Deploy to local Forge
├── deploy-test.sh # Deploy to test environment
├── deploy-prod.sh # Deploy to production
├── generate-blueprint.sh # Generate deployment blueprint
├── schema/
│ └── init.sql # Database initialization
└── src/
├── main/
│ ├── java/.../
│ │ └── myfirstslice/
│ │ └── MyFirstSlice.java # Sample slice
│ └── resources/
│ └── slices/
│ └── MyFirstSlice.toml # Slice runtime config
└── test/java/.../
└── myfirstslice/
└── MyFirstSliceTest.java
Notice: there's only one Java source file. In Aether, the slice interface, request/response types, error types, factory method, and implementation all live together in one file. This is deliberate — related code stays together.
mvn clean verify
This runs the full pipeline:
target/blueprint.toml@Slice
public interface MyFirstSlice {
record Request(String value) {
public static Result<Request> request(String value) {
return Verify.ensure(value, Verify.Is::notBlank, new ValidationError.EmptyValue())
.map(Request::new);
}
}
record Response(String result) {}
sealed interface ValidationError extends Cause {
record EmptyValue() implements ValidationError {
@Override
public String message() {
return "Value cannot be empty";
}
}
static ValidationError emptyValue() {
return new EmptyValue();
}
}
Promise<Response> process(Request request);
static MyFirstSlice myFirstSlice() {
return request -> Promise.success(new Response("Processed: " + request.value()));
}
}
Key elements:
@Slice — marks this interface for annotation processingmyFirstSlice() creates the implementation. Dependencies would be parameters herePromise<T> for non-blocking execution# Start a local 5-node cluster simulator
./run-forge.sh
Forge starts a 5-node cluster on your laptop — real consensus, real leader election, real routing. Key ports:
| Service | Port | URL |
|---|---|---|
| Dashboard | 8888 | http://localhost:8888 |
| App HTTP (via LB) | 8080 | http://localhost:8080 |
| Management API | 5150 | http://localhost:5150 |
Open the dashboard to see live metrics, cluster topology, and node status. Stop Forge with Ctrl+C.
# In another terminal
./deploy-forge.sh
This builds a blueprint artifact, uploads it to the running Forge cluster, and deploys it. You should see the slice activate across nodes in the dashboard.
Test it (the URL path depends on your routes.toml configuration):
curl -s -X POST http://localhost:8080/api/v1/my-first-slice/ \
-H "Content-Type: application/json" \
-d '{"value": "hello"}' | jq