Preserving AWS protocol compatibility is the first principle of every change in Floci. The AWS SDK must work against Floci without modification — never introduce custom endpoint shapes or convenience abstractions that diverge from real AWS wire protocols.
Project overview
Floci is a Java/Quarkus-based local AWS emulator. Its goal is full AWS SDK and CLI compatibility through real AWS wire protocols, not simplified abstractions. It listens on port 4566 and acts as an open-source alternative to LocalStack Community.Stack
| Component | Version |
|---|---|
| Java | 25 |
| Quarkus | 3.32.3 |
| JUnit | 5 |
| RestAssured | (via Quarkus BOM) |
| Jackson | (via Quarkus BOM) |
| Docker integrations | Lambda, RDS, ElastiCache |
Architecture overview
Incoming requests from the AWS SDK or CLI arrive over HTTP at port 4566. The HTTP router dispatches each request to the appropriate service handler based on protocol and path. Stateless services (SSM, SQS, IAM, etc.) and stateful services (S3, DynamoDB) write through to a pluggableStorageBackend. Container services (Lambda, ElastiCache, RDS) delegate to real Docker containers.
StorageBackend supports four modes: memory, persistent, hybrid, and wal.
Layered design
Floci follows a strict three-layer architecture within each service:| Layer | Responsibility |
|---|---|
| Controller / Handler | Parses AWS protocol input; produces AWS-compatible responses |
| Service | Contains business logic; throws AwsException for domain errors |
| Model | Domain objects (POJOs) |
Core infrastructure components
| Component | Role |
|---|---|
EmulatorConfig | Central configuration interface; all settings live under floci.* |
ServiceRegistry | Registers and wires all service instances at startup |
StorageBackend + StorageFactory | Pluggable persistence abstraction; always access via StorageFactory |
AwsJson11Controller | Base controller for AWS JSON 1.1 protocol services |
AwsQueryController | Base controller for AWS Query protocol services |
AwsException + AwsExceptionMapper | Structured AWS error responses across protocols |
EmulatorLifecycle | Manages startup and shutdown hooks, including storage load/flush |
Package layout
*Controller.java, a *Service.java, and a model/ sub-package for domain objects.
AWS protocol reference
Floci implements the real AWS wire protocols. Choosing the correct protocol for a new service is required — never use a custom endpoint shape.| Protocol | Services | Request format | Response format | Implementation |
|---|---|---|---|---|
| Query | SQS, SNS, IAM, STS, RDS, ElastiCache, CloudFormation, CloudWatch Metrics | form-encoded POST + Action | XML | AwsQueryController |
| JSON 1.1 | SSM, EventBridge, CloudWatch Logs, Kinesis, KMS, Cognito, Secrets Manager, ACM | POST + X-Amz-Target | JSON | AwsJson11Controller |
| REST JSON | Lambda, API Gateway | REST paths | JSON | JAX-RS |
| REST XML | S3 | REST paths | XML | JAX-RS |
| TCP | ElastiCache, RDS | raw protocol | native | proxies |
- CloudWatch Metrics supports both Query and JSON 1.1; keep handlers aligned.
- SQS and SNS may expose multiple compatibility paths; do not let them drift.
- Cognito well-known endpoints are OIDC REST JSON endpoints, not standard AWS management APIs.
Service implementation pattern
Identify the AWS protocol
Consult the protocol table above. Every service maps to exactly one primary protocol. Using the wrong protocol breaks SDK compatibility.
Mirror an existing service
Find the closest existing service in
src/main/java/.../services/ and use it as a template. Copy the pattern; do not invent new ones.Keep controllers thin
Controllers parse requests and serialize responses. All logic belongs in the service layer. Controllers must not contain business decisions.
Use AwsException for domain errors
Services must throw
AwsException rather than generic exceptions. Query and REST XML flows use AwsExceptionMapper; JSON 1.1 flows return structured AWS error responses.Adding a new service
Create the service package
Add a new package under
src/main/java/io/github/hectorvent/floci/services/<service>/.Add the three layers
Create:
<Service>Controller.java— extends the appropriate base controller for your protocol<Service>Service.java— annotated@ApplicationScoped, contains all business logicmodel/— POJOs for request and response objects
Add config to EmulatorConfig
Add any service-specific configuration fields to
EmulatorConfig.java. Follow the floci.* namespace and FLOCI_* environment variable conventions.Add YAML config in main and test config
Update both
src/main/resources/application.yml and src/test/resources/application.yml with the new config entries. Treat repository YAML as the source of truth for runtime behavior.Wire storage through StorageFactory
If your service persists state, always obtain storage through
StorageFactory. Never instantiate storage implementations directly inside a service. Respect lifecycle hooks for load and flush behavior.Add tests
Add
<Service>IntegrationTest.java for integration tests and, where appropriate, <Service>ServiceTest.java for unit tests. Prefer AWS SDK clients over raw HTTP for validation.Testing conventions
| Test class suffix | Type | Notes |
|---|---|---|
*ServiceTest.java | Unit | Tests service logic in isolation; prefer package-private constructors |
*IntegrationTest.java | Integration | Tests the full request/response path; may use ordered execution for stateful behavior |
Storage rules
- Always use
StorageFactoryto obtain aStorageBackendinstance. - Never instantiate storage implementations (
MemoryStorageBackend, etc.) directly inside services. - Respect
EmulatorLifecyclehooks — storage must load on startup and flush on shutdown. - When adding storage-related behavior: update
EmulatorConfig, update bothapplication.ymlfiles, wire throughStorageFactory, and verify lifecycle integration.
Common mistakes to avoid
Common mistakes to avoid
These are the most frequent errors when adding or modifying services in Floci:
- Creating non-AWS endpoints — all endpoints must match real AWS wire protocol shapes. The SDK must work without modification.
- Bypassing StorageFactory — never instantiate storage backends directly; always go through
StorageFactory. - Changing wire formats without tests — any change to request parsing, response shape, or error handling requires updated automated tests.
- Forgetting YAML updates — config added to
EmulatorConfigwithout corresponding entries inapplication.ymlwill break at runtime or in tests. - Producing inconsistent URLs or ARNs — ARNs and resource URLs must match the format AWS returns; use existing helpers.
- Testing only with raw HTTP — raw HTTP does not catch SDK serialization issues. Use AWS SDK clients for integration validation.
- Introducing unnecessary new patterns — copy an existing service structure. New patterns require maintainer discussion before introduction.
- Broad refactors in feature PRs — keep changes focused. If a task seems to require broad architectural changes, surface the tradeoffs instead of refactoring across services.