FractalX Framework
Decompose a Spring Boot modular monolith into a production-ready microservice platform with a single Maven command.
What FractalX generates
For every annotated domain module a fully independent Spring Boot service is produced, plus four shared infrastructure services per project.
Design principles
| Principle | What it means |
|---|---|
| Annotation-minimal | One @DecomposableModule per module - the entire developer contract |
| Zero-intrusion | No changes to business logic, entities, or service classes |
| Safe-default | Unknown dependencies always kept; unknown code always copied |
| Incremental | Re-running decompose replaces only changed output |
| Observable | Every generated service ships with tracing, metrics and health checks on day one |
Installation
Add FractalX to an existing Spring Boot Maven project in two steps.
System requirements
| Component | Minimum | Notes |
|---|---|---|
| JDK | 17 | Tested on OpenJDK 17, 21. Java 24 needs -Djacoco.skip=true |
| Maven | 3.8 | Maven wrapper mvnw recommended |
| Spring Boot | 3.2.x | Spring Boot 2.x not supported |
| PostgreSQL | 14+ | Or MySQL 8+, MongoDB 6+ |
| Docker | 24+ | Optional - container deployment only |
Add to pom.xml
<properties>
<fractalx.version>0.2.4</fractalx.version>
</properties>
<!-- 1. Annotation library (compile-time only) -->
<dependencies>
<dependency>
<groupId>org.fractalx</groupId>
<artifactId>fractalx-annotations</artifactId>
<version>${fractalx.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!-- 2. Maven plugin -->
<build><plugins><plugin>
<groupId>org.fractalx</groupId>
<artifactId>fractalx-maven-plugin</artifactId>
<version>${fractalx.version}</version>
</plugin></plugins></build>Verify
$ mvn fractalx:help
FractalX Maven Plugin 0.2.4
Available goals:
fractalx:decompose Decompose monolith into microservices
fractalx:menu Interactive decomposition menu
fractalx:help Display this helpQuick Start
Convert a two-module monolith into two independently deployable microservices.
For each bounded context, add a plain class annotated with @DecomposableModule.
@DecomposableModule(serviceName = "order-service", port = 8081)
public class OrderModule { }@DecomposableModule(serviceName = "payment-service", port = 8082)
public class PaymentModule {
// Declare cross-module deps - FractalX wires via NetScope
private OrderService orderService;
}$ mvn fractalx:decompose
[INFO] FractalX 0.2.4 - starting decomposition
[INFO] Phase 1: Parsing 22 source files ...
[INFO] Phase 2: Detected 2 modules, 1 cross-module dependency
[INFO] Generating order-service (port 8081) ...
[INFO] Generating payment-service (port 8082) ...
[INFO] Generating gateway, registry, admin, logger ...
[INFO] BUILD SUCCESS [610ms]# Option A - Docker Compose (recommended)
$ docker-compose -f fractalx-output/docker-compose.yml up -d
# Option B - start script
$ ./fractalx-output/start-all.shProject Structure
What mvn fractalx:decompose writes to disk.
fractalx-output/
├── order-service/
│ ├── pom.xml
│ ├── Dockerfile
│ └── src/main/
│ ├── java/com/fractalx/generated/orderservice/
│ │ ├── OrderServiceApplication.java
│ │ ├── OtelConfig.java
│ │ ├── ServiceHealthConfig.java
│ │ └── ResilienceConfig.java
│ ├── java/com/myapp/order/ ← copied business logic
│ │ ├── OrderService.java ← @NetworkPublic added
│ │ └── OrderController.java
│ ├── java/com/myapp/order/client/
│ │ └── PaymentServiceClient.java ← NetScope client
│ └── resources/
│ ├── application.yml
│ ├── application-dev.yml
│ ├── application-docker.yml
│ └── db/migration/V1__init.sql
├── payment-service/ ← same structure
├── fractalx-gateway/
├── fractalx-registry/
├── admin-service/
├── logger-service/
├── docker-compose.yml
└── start-all.shInfrastructure services
Generated file types
| File | Description |
|---|---|
pom.xml | Maven build with filtered + pruned dependencies |
Dockerfile | Multi-stage build: builder + slim runtime |
application.yml | Base Spring Boot configuration |
application-dev.yml | Local dev overrides (localhost URLs, Swagger UI) |
application-docker.yml | Container-ready overrides with service DNS names |
OtelConfig.java | OpenTelemetry OTLP exporter |
ResilienceConfig.java | Circuit breaker + retry + time limiter per dependency |
*ServiceClient.java | NetScope @NetScopeClient interface |
V1__init.sql | Flyway initial schema scaffold from @Entity classes |
docker-compose.yml | Full platform - all services + Jaeger |
Annotations
FractalX's developer API is four annotations. Two are generated internally - do not use them manually.
Primary annotation. Marks a class as the boundary definition for a microservice. Place one per package subtree.
@DecomposableModule(
serviceName = "order-service", // kebab-case, required
port = 8081, // HTTP port, required
independentDeployment = true, // default: false
ownedSchemas = {"orders", "order_items"}
)
public class OrderModule {
private PaymentService paymentService; // cross-module dep
}| Attribute | Type | Default | Description |
|---|---|---|---|
serviceName | String | - | Service directory name + spring.application.name. Required. |
port | int | - | HTTP port. gRPC auto-set to port + 10000. Required. |
independentDeployment | boolean | false | No runtime dependency on other generated services. |
ownedSchemas | String[] | {} | Scopes Flyway migrations and JPA boundaries. |
Explicitly marks a method as cross-service callable. Without it, FractalX infers boundaries automatically.
@ServiceBoundary
public boolean isConfirmed(Long orderId) {
return repository.existsByIdAndStatus(orderId, CONFIRMED);
}
// stays internal - not exposed to other services
public void recalculateTotal(Order order) { ... }Includes this module in admin management endpoints, health summary, and topology map.
@DecomposableModule(serviceName = "order-service", port = 8081)
@AdminEnabled
public class OrderModule { }Marks a method spanning multiple services. Generates a fractalx-saga-orchestrator at port 8099 with forward steps and compensation logic.
@DistributedSaga(sagaId = "checkout", compensation = "cancelCheckout")
public void processCheckout(Long orderId, Long userId) {
orderService.confirm(orderId);
paymentService.charge(userId, orderService.getTotal(orderId));
inventoryService.reserve(orderId);
}
public void cancelCheckout(Long orderId, Long userId) {
paymentService.refund(userId);
orderService.cancel(orderId);
}Cross-Module Dependencies
FractalX detects inter-module dependencies automatically via two-phase AST analysis - no explicit declarations needed.
How detection works
Phase 1 parses every .java file into an in-memory AST map. Phase 2 maps each @DecomposableModule to its package prefix, then finds every import referencing a type from a different module's package - these are the cross-module dependencies.
Declaring dependencies
@DecomposableModule(serviceName = "leave-service", port = 8084)
public class LeaveModule {
private EmployeeService employeeService;
private DepartmentService departmentService;
}import com.myapp.employee.EmployeeService;
import com.myapp.department.DepartmentService;
@Service
public class LeaveService {
public LeaveService(LeaveRepository repo,
EmployeeService emp,
DepartmentService dept) { ... }
}What gets generated (A → B)
| Location | Artefact | Description |
|---|---|---|
service-A | BServiceClient.java | @NetScopeClient interface - drop-in for BService |
service-A | Resilience4j config | CircuitBreaker, Retry, TimeLimiter for the B dep |
service-B | @NetworkPublic | Added to each called public method, exposes over gRPC |
service-B | gRPC port | Auto-set to B.port + 10000 |
PaymentService → strip suffix → kebab-case → payment-serviceNetScope
FractalX's gRPC-based inter-service layer. Replaces Feign with zero-config, strongly typed, resilience-wrapped remote calls.
:8081 → gRPC :18081. Applied automatically.Generated client
@NetScopeClient(server = "payment-service", beanName = "paymentService")
public interface PaymentServiceClient {
boolean charge(Long userId, BigDecimal amount);
void refund(Long userId);
}application.yml - NetScope section
netscope:
server:
enabled: true
grpc.port: 18081 # port + 10000
client.servers:
payment-service:
host: ${PAYMENT_HOST:localhost}
port: 18082fractalx-config.yml
Place alongside pom.xml. Read at generation time to populate service-specific overrides.
application.yml then application.properties. All values fall back to safe defaults.fractalx:
registry-url: http://fractalx-registry:8761
logger-url: http://logger-service:9099
otel-endpoint: http://jaeger:4317
gateway-port: 9999
admin-port: 9090
gateway:
security:
bearer.enabled: false
oauth2.enabled: false
api-key.enabled: false
cors:
allowed-origins: "*"
allowed-methods: "GET,POST,PUT,DELETE,OPTIONS"
services:
order-service:
datasource:
url: jdbc:postgresql://localhost:5432/order_db
username: postgres
password: secretMaven Plugin
Full reference for goals, configuration, and command-line overrides.
Goals
| Goal | Phase | Description |
|---|---|---|
fractalx:decompose | generate-sources | Runs the full decomposition pipeline |
fractalx:menu | none | Interactive terminal menu. Falls back to numbered input on Windows. |
fractalx:help | none | Prints available goals and plugin version |
Plugin configuration
<plugin>
<groupId>org.fractalx</groupId>
<artifactId>fractalx-maven-plugin</artifactId>
<version>0.2.4</version>
<configuration>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<outputDirectory>${project.basedir}/fractalx-output</outputDirectory>
<configFile>${project.basedir}/fractalx-config.yml</configFile>
<generateDocker>true</generateDocker>
<generateAdmin>true</generateAdmin>
</configuration>
</plugin>Command-line overrides
$ mvn fractalx:decompose -DoutputDirectory=./my-services
$ mvn fractalx:decompose -DgenerateDocker=false
$ mvn fractalx:decompose -Djacoco.skip=true # JDK 24
$ mvn fractalx:decompose -Dfractalx.verbose=trueObservability
Distributed tracing, health metrics, and correlation IDs - zero developer action required.
Distributed tracing - OpenTelemetry
Each service gets an OtelConfig.java configuring the OTLP/gRPC exporter pointing at Jaeger. W3C traceparent headers propagate through all HTTP and gRPC calls automatically.
docker-compose.yml.Health metrics - Micrometer
ServiceHealthConfig.java registers a TCP health check per dependency and exposes a Micrometer gauge fractalx.service.dependency.up for each one. Scraped by Prometheus and visualised on the Admin Dashboard.
Correlation IDs
| Header | Description |
|---|---|
X-Request-Id | Unique per HTTP request (UUID) |
X-Correlation-Id | Spans a logical operation across multiple service calls - set once at gateway, propagated |
Resilience
Three Resilience4j components auto-configured per cross-module dependency in ResilienceConfig.java.
| Component | Default behaviour | Config key |
|---|---|---|
| Circuit Breaker | Opens after 50% failure rate over 10-call window. Half-open after 30s. | resilience4j.circuitbreaker |
| Retry | 3 attempts, 500ms exponential backoff for IOException + TimeoutException | resilience4j.retry |
| Time Limiter | 2s timeout per call before raising TimeoutException | resilience4j.timelimiter |
Data Management
Database isolation, schema migrations, and distributed saga orchestration.
Database isolation
When ownedSchemas is set, each service gets scoped @EntityScan + @EnableJpaRepositories, datasource properties from fractalx-config.yml, and Flyway migrations at db/migration/V1__init.sql.
ReferenceValidator bean replacing FK integrity with a NetScope exists() call at write time. Review ReferenceValidatorConfig.java before production.Saga orchestration
Methods annotated with @DistributedSaga trigger a dedicated fractalx-saga-orchestrator service at port 8099.
| Endpoint | Description |
|---|---|
POST /saga/{sagaId}/start | Start a saga instance - returns correlationId |
GET /saga/status/{correlationId} | Poll: STARTED → IN_PROGRESS → DONE / FAILED |
GET /saga | List all definitions and recent executions |
API Gateway
Spring Cloud Gateway at :9999 - dynamic routing, auth, rate limiting and observability, generated automatically.
| Capability | Description | Default |
|---|---|---|
| Dynamic routing | Routes fetched live from registry; static YAML fallback | On |
| Rate limiting | Token-bucket per IP+service; no Redis required | On |
| CORS | CorsWebFilter; origins/methods config-driven | On |
| Circuit breaker | Resilience4j per route + GatewayFallbackController | On |
| Request logging | Method, path, status, duration on every request | On |
| Tracing filter | Injects X-Request-Id, X-Correlation-Id, traceparent | On |
| JWT (HMAC) | Bearer token validation with shared secret | Off |
| OAuth2 | JWK Set URI (Keycloak, Auth0…) | Off |
| API Key | X-Api-Key header or ?api_key= param | Off |
Admin Dashboard
Full-stack management interface at http://localhost:9090. No additional setup required.
| Section | Content |
|---|---|
| Overview | Service count, health summary, recent alerts |
| Services | Per-service status, uptime, instance count |
| Network Map | Interactive topology graph of all services and dependencies |
| gRPC / NetScope | Active connections, call counts, latency per dependency |
| API Explorer | Live REST client against any generated service |
| Observability | Distributed traces (Jaeger embedded), log search |
| Alerts | Rules with SSE, webhook, Slack, email channels |
| Config Editor | View and override FractalxConfig at runtime |
| Data Consistency | Outbox event queue status, saga instance tracker |
Deploying Your App
Three options: Docker Compose, start script, or run services individually.
Docker Compose
$ docker-compose -f fractalx-output/docker-compose.yml up -d
$ docker-compose -f fractalx-output/docker-compose.yml logs -f order-service
$ docker-compose -f fractalx-output/docker-compose.yml downLocal development
# 1. Start registry first
$ cd fractalx-output/fractalx-registry && mvn spring-boot:run
# 2. Start domain services with dev profile
$ cd fractalx-output/order-service
$ mvn spring-boot:run -Dspring.profiles.active=dev
# Or use the generated script
$ ./fractalx-output/start-all.shService startup order
Port Reference
Default port assignments for all services in a FractalX platform.
| Service | HTTP | gRPC | Description |
|---|---|---|---|
fractalx-registry | 8761 | - | Service registry |
fractalx-gateway | 9999 | - | API gateway |
admin-service | 9090 | - | Admin dashboard |
logger-service | 9099 | - | Log sink |
| Domain service 1 | 8081 | 18081 | First domain service |
| Domain service 2 | 8082 | 18082 | Second domain service |
| Domain service n | 808n | 1808n | n-th domain service |
| Jaeger | 16686 | 4317 | Distributed trace UI + OTLP |
port in @DecomposableModule (1–65535). gRPC is always port + 10000. No two modules may share the same port.Troubleshooting
Fixes for the most common issues.
1. Class not in scanned source root - verify <sourceDirectory> in plugin config.
2. Annotation on inner class - move to a top-level class.
3. Missing/wrong-scope dependency - run mvn dependency:list | grep fractalx.
1. Field uses interface type, not concrete class. Use BService, not IBService.
2. Import missing (IDE auto-imported different class). Check import statements.
3. Declare explicitly: private BService bService; in AModule.
$ mvn fractalx:decompose -Djacoco.skip=true1. Create fractalx-config.yml with datasource URL before running decompose.
2. Set env var: export ORDER_DB_URL=jdbc:postgresql://localhost:5432/order_db
3. Use Docker Compose - it starts database containers automatically.
spring.flyway:
baseline-on-migrate: true
baseline-version: 0Diagnostic commands
$ mvn fractalx:decompose -Dfractalx.verbose=true
$ curl http://localhost:8761/api/services | jq .
$ curl http://localhost:9999/actuator/gateway/routes | jq .
$ curl http://localhost:9090/api/health/summary | jq .
$ curl http://localhost:9090/api/topology | jq .FAQ
Answers to the most frequently asked questions.
Yes. FractalX only parses source files - it doesn't compile or run them. Add @DecomposableModule to one class per bounded context and run decompose. No modification to existing code is required.
No. FractalX is read-only on the monolith source tree. All generated code is written to fractalx-output/ by default.
Yes. Each run replaces the output directory. Safe to run iteratively as you refine module boundaries.
PostgreSQL, MySQL, and MongoDB datasource configuration is generated from fractalx-config.yml. H2 is supported for testing.
No. FractalX targets Spring Boot 3.2.x and Jakarta EE namespaces. Spring Boot 2.x uses javax.* namespaces which are incompatible with the generated code.
Classes imported by multiple modules are copied into each service. Extract shared utilities to a common library before decomposition if intentional sharing is needed.
Implement the ServiceFileGenerator interface and register it in ServiceGenerator.buildPipeline(). Each step receives a GenerationContext with module descriptor, output path, and platform config.