Skip to main content
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

ComponentVersion
Java25
Quarkus3.32.3
JUnit5
RestAssured(via Quarkus BOM)
Jackson(via Quarkus BOM)
Docker integrationsLambda, 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 pluggable StorageBackend. Container services (Lambda, ElastiCache, RDS) delegate to real Docker containers.
AWS SDK / CLI

     ▼ HTTP :4566 (AWS wire protocol)
 HTTP Router (JAX-RS / Vert.x)

     ├──► Stateless Services  ──► StorageBackend
     │    SSM · SQS · SNS · IAM · STS · KMS
     │    Secrets Manager · Cognito · Kinesis
     │    EventBridge · CloudWatch · Step Functions
     │    CloudFormation · ACM · API Gateway · SES

     ├──► Stateful Services   ──► StorageBackend
     │    S3 · DynamoDB · DynamoDB Streams

     └──► Container Services  ──► Docker Engine
          Lambda · ElastiCache · RDS
StorageBackend supports four modes: memory, persistent, hybrid, and wal.

Layered design

Floci follows a strict three-layer architecture within each service:
LayerResponsibility
Controller / HandlerParses AWS protocol input; produces AWS-compatible responses
ServiceContains business logic; throws AwsException for domain errors
ModelDomain objects (POJOs)
Controllers must stay thin. Business logic belongs in the service layer. Models are plain data objects with no behavior.

Core infrastructure components

ComponentRole
EmulatorConfigCentral configuration interface; all settings live under floci.*
ServiceRegistryRegisters and wires all service instances at startup
StorageBackend + StorageFactoryPluggable persistence abstraction; always access via StorageFactory
AwsJson11ControllerBase controller for AWS JSON 1.1 protocol services
AwsQueryControllerBase controller for AWS Query protocol services
AwsException + AwsExceptionMapperStructured AWS error responses across protocols
EmulatorLifecycleManages startup and shutdown hooks, including storage load/flush

Package layout

io.github.hectorvent.floci
├── config/                   # EmulatorConfig
├── core/
│   ├── common/               # Shared utilities (XmlBuilder, XmlParser, AwsNamespaces, …)
│   └── storage/              # StorageBackend, StorageFactory, storage mode implementations
├── lifecycle/                # EmulatorLifecycle
└── services/
    ├── ssm/
    │   ├── SsmController.java
    │   ├── SsmService.java
    │   └── model/
    ├── sqs/
    ├── s3/
    └── <service>/            # One package per AWS service
Each service package follows the same structure: a *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.
ProtocolServicesRequest formatResponse formatImplementation
QuerySQS, SNS, IAM, STS, RDS, ElastiCache, CloudFormation, CloudWatch Metricsform-encoded POST + ActionXMLAwsQueryController
JSON 1.1SSM, EventBridge, CloudWatch Logs, Kinesis, KMS, Cognito, Secrets Manager, ACMPOST + X-Amz-TargetJSONAwsJson11Controller
REST JSONLambda, API GatewayREST pathsJSONJAX-RS
REST XMLS3REST pathsXMLJAX-RS
TCPElastiCache, RDSraw protocolnativeproxies
Important exceptions:
  • 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

1

Identify the AWS protocol

Consult the protocol table above. Every service maps to exactly one primary protocol. Using the wrong protocol breaks SDK compatibility.
2

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.
3

Keep controllers thin

Controllers parse requests and serialize responses. All logic belongs in the service layer. Controllers must not contain business decisions.
4

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.
5

Reuse shared utilities

Use XmlBuilder for XML responses, XmlParser for XML parsing (never regex), and AwsNamespaces constants. JSON errors must follow AWS error structures.

Adding a new service

1

Create the service package

Add a new package under src/main/java/io/github/hectorvent/floci/services/<service>/.
2

Add the three layers

Create:
  • <Service>Controller.java — extends the appropriate base controller for your protocol
  • <Service>Service.java — annotated @ApplicationScoped, contains all business logic
  • model/ — POJOs for request and response objects
3

Register in ServiceRegistry

Add the new service to ServiceRegistry so it is wired at startup.
4

Add config to EmulatorConfig

Add any service-specific configuration fields to EmulatorConfig.java. Follow the floci.* namespace and FLOCI_* environment variable conventions.
5

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.
6

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.
7

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.
8

Update documentation

Document any user-facing configuration, behavior, or limitations. Reference the compatibility test suite when changes may affect real SDK behavior.

Testing conventions

Test class suffixTypeNotes
*ServiceTest.javaUnitTests service logic in isolation; prefer package-private constructors
*IntegrationTest.javaIntegrationTests the full request/response path; may use ordered execution for stateful behavior
Prefer SDK-based validation over raw HTTP. When a change affects request parsing, response shape, error handling, or persistence semantics, add or update automated tests before finishing.

Storage rules

  • Always use StorageFactory to obtain a StorageBackend instance.
  • Never instantiate storage implementations (MemoryStorageBackend, etc.) directly inside services.
  • Respect EmulatorLifecycle hooks — storage must load on startup and flush on shutdown.
  • When adding storage-related behavior: update EmulatorConfig, update both application.yml files, wire through StorageFactory, and verify lifecycle integration.
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 EmulatorConfig without corresponding entries in application.yml will 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.

Build docs developers (and LLMs) love