A collection of example applications demonstrating various features and capabilities of the Xorcery framework - a modern, reactive microservices framework for Java.
- About Xorcery
- Prerequisites
- Quick Start
- Examples Overview
- Building and Running
- Docker Compose Setup
- Project Structure
- Configuration
- Key Technologies
- Migration to Xorcery 0.166.9
- Troubleshooting
- Documentation
- Contributing
Xorcery is a high-performance, reactive microservices framework built on top of:
- HK2 for dependency injection
- Jetty 12 for HTTP/WebSocket server
- Jersey 3 for REST APIs
- Project Reactor for reactive streams
- Neo4j for graph database projections
- OpenTelemetry for observability
It provides a modular, event-driven architecture with built-in support for:
- Domain-driven design (DDD) with event sourcing
- Reactive streams and backpressure
- WebSocket-based streaming
- Graph database projections
- JWT authentication
- mTLS support
- Service discovery via DNS
- Java 21 or higher
- Maven 3.8+ for building
- Docker & Docker Compose (optional, for running dependencies)
For the examples, you may need:
- Neo4j (graph database for projections) - can run embedded
- EventStore (event sourcing database) - for Forum example
- OpenSearch (search and analytics) - for Forum example
These can be started using the provided docker-compose.yaml.
# Clone the repository
git clone https://github.com/Cantara/xorcery-examples.git
cd xorcery-examples
# Build all examples
mvn clean install
# Run the Greeter example
cd xorcery-examples-greeter
mvn exec:java -Dexec.mainClass="dev.xorcery.runner.Main"
# Or run tests
mvn testLocation: xorcery-examples-greeter/
A simple microservice demonstrating:
- REST API with JAX-RS resources
- Domain events with event sourcing
- Neo4j projections for read models
- Reactive streams for event processing
- Thymeleaf for server-side rendering
- WebSocket streaming for real-time updates
Key Features:
GreeterApplication- Command handler for updating greetingsGreeterService- Manages Neo4j projection subscriptionsGreeterResource- REST API endpoint (/api/greeter)- Domain event publishing and projection
API Endpoints:
GET /api/greeter- Retrieve current greetingPOST /api/greeter- Update greeting with form parameter
Running:
cd xorcery-examples-greeter
mvn clean install
java -jar target/xorcery-examples-greeter-*.jarTesting:
# Run the test
mvn test
# Or manually test the API
curl https://localhost:8443/api/greeter --insecure
curl -X POST https://localhost:8443/api/greeter -d "greeting=Hello%20Xorcery!" --insecureLocation: xorcery-examples-forum/
A comprehensive forum application showcasing:
- JSON:API compliant REST endpoints
- Complex domain model (Posts, Comments, Users)
- Event sourcing with EventStore
- Graph database for efficient querying
- JWT authentication
- Service discovery via DNS
- Thymeleaf templating for web UI
Domain Model:
- Posts with title, content, and metadata
- Comments on posts
- User authentication and authorization
Key Components:
ForumApplication- Command handlersForumModel- Domain logic- REST resources for Posts, Comments, Forum operations
- Neo4j projections for read models
Running:
# Start dependencies (EventStore, OpenSearch)
docker-compose up -d xe-eventstore xe-opensearch
# Build and run
cd xorcery-examples-forum
mvn clean install
java -jar target/xorcery-examples-forum-*.jarLocation: xorcery-examples-streaming/
Demonstrates reactive stream processing with multiple processors in a pipeline:
Architecture:
Source → Processor1 → Processor2 → Processor3 → Result
Components:
- SourceService - Publishes a stream of numbers (0-99) via WebSocket
- Processor1Service - Adds a "processor1" field with value "foo"
- Processor2Service - Adds "processor2" field with value + 1
- Processor3Service - Adds "processor3" field with value * 3
- ResultService - Subscribes to the stream and prints results
Key Features:
- WebSocket-based streaming with backpressure
- Chain multiple processors dynamically
- Reactive transformations using Project Reactor
- Service composition through configuration
- Demonstrates the reactive streams client/server API
Running: You can run each service independently or configure them to chain together:
# Run the complete pipeline (all processors configured)
cd xorcery-examples-streaming
mvn clean install
java -jar target/xorcery-examples-streaming-*.jar
# Run tests to see the pipeline in action
mvn testLocation: xorcery-examples-persistentsubscriber/
Shows how to create Neo4j event projections to handle domain events persistently and build read models.
⚠️ Important: This example has been updated for Xorcery 0.166.9+. The oldxorcery-reactivestreams-persistentsubscribermodule has been replaced with the Neo4j projections API.
Key Features:
ExamplePersistentSubscriber- ImplementsNeo4jEventProjectioninterface- Filters and processes specific events (e.g., "CreateApplication" command)
- Writes events to Neo4j within transactions
- Automatic retry and error handling
- State management through Neo4j graph database
What Changed from Previous Versions:
- Old API: Extended
BasePersistentSubscriberfromxorcery-reactivestreams-persistentsubscriber - New API: Implements
Neo4jEventProjectionfromxorcery-neo4j-projections - Benefits:
- ✅ Better integration with Neo4j
- ✅ Transactional guarantees
- ✅ More flexible event handling
- ✅ Direct access to Neo4j transaction for complex graph operations
Use Cases:
- Building read models from event streams
- Creating graph-based projections from domain events
- Event-driven integrations
- Audit logging
- Real-time analytics
Running:
cd xorcery-examples-persistentsubscriber
mvn clean install
java -jar target/xorcery-examples-persistentsubscriber-*.jar
# Run tests
mvn testConfiguration Example:
# Neo4j Projections
neo4jprojections:
enabled: true
projections:
- name: "examplesubscriber" # Matches @Service(name = "examplesubscriber")
stream: "applications" # Stream to subscribe to
batchSize: 100 # Events per batch
timeout: 30 # Timeout in secondsmvn clean installcd xorcery-examples-greeter
mvn clean installmvn exec:java -Dexec.mainClass="dev.xorcery.runner.Main"java -jar target/xorcery-examples-greeter-1.166.1-SNAPSHOT.jar# All tests
mvn test
# Specific module
mvn test -pl xorcery-examples-greeter
# Specific test
mvn test -Dtest=GreeterResourceTestBuild native installers for your platform:
mvn clean install -P jpackageThe installer will be in target/jpackage/.
The repository includes a docker-compose.yaml for running external dependencies:
# Start all services
docker-compose up -d
# Start specific services
docker-compose up -d xe-eventstore xe-opensearch
# View logs
docker-compose logs -f
# Stop all services
docker-compose down
# Remove volumes (clean slate)
docker-compose down -vServices included:
- xe-eventstore - EventStore for event sourcing (ports 1113, 2113)
- xe-opensearch - OpenSearch for search/analytics (port 9200)
- xe-opensearch-dashboards - Kibana-like UI (port 5601)
xorcery-examples/
├── pom.xml # Parent POM with dependency versions
├── docker-compose.yaml # External dependencies
├── README.md # This file
│
├── xorcery-examples-greeter/ # Simple REST + Events example
│ ├── src/main/java/
│ │ ├── com/exoreaction/xorcery/examples/greeter/
│ │ │ ├── GreeterApplication.java # Command handler
│ │ │ ├── GreeterService.java # Projection service
│ │ │ ├── commands/
│ │ │ │ └── UpdateGreeting.java
│ │ │ └── resources/api/
│ │ │ └── GreeterResource.java # REST API
│ │ └── module-info.java
│ ├── src/main/resources/
│ │ └── META-INF/xorcery.yaml # Configuration
│ └── pom.xml
│
├── xorcery-examples-forum/ # Complex forum application
│ ├── src/main/java/
│ │ └── com/exoreaction/xorcery/examples/forum/
│ │ ├── ForumService.java
│ │ ├── contexts/
│ │ ├── entities/
│ │ ├── model/
│ │ └── resources/
│ └── pom.xml
│
├── xorcery-examples-streaming/ # Stream processing pipeline
│ ├── src/main/java/
│ │ └── com/exoreaction/xorcery/examples/streaming/
│ │ ├── source/
│ │ │ └── SourceService.java # Stream source
│ │ ├── processors/
│ │ │ ├── ProcessorService.java # Base processor
│ │ │ ├── Processor1Service.java
│ │ │ ├── Processor2Service.java
│ │ │ └── Processor3Service.java
│ │ └── result/
│ │ └── ResultService.java # Stream consumer
│ └── pom.xml
│
└── xorcery-examples-persistentsubscriber/ # Neo4j projection example
├── src/main/java/
│ └── com/exoreaction/xorcery/examples/persistentsubscriber/
│ └── ExamplePersistentSubscriber.java # Implements Neo4jEventProjection
├── src/main/resources/
│ └── META-INF/xorcery.yaml
├── src/test/java/
│ └── com/exoreaction/xorcery/examples/persistentsubscriber/test/
│ └── ExamplePersistentSubscriberTest.java
└── pom.xml
Each example has its own xorcery.yaml configuration file in src/main/resources/META-INF/.
# Instance configuration
instance:
id: "{{ instance.host }}"
host: "{{ CALCULATED.hostName }}"
domain: "xorcery.test"
environment: "development"
# Application metadata
application:
name: "xorcery-example"
version: "1.166.1-SNAPSHOT"
# Jetty server
jetty.server:
http:
enabled: true
port: 8080
ssl:
enabled: true
port: 8443
# SSL/TLS certificates
certificates:
enabled: true
dnsNames:
- localhost
ipAddresses:
- 127.0.0.1
# Keystores
keystores:
enabled: true
keystore:
path: "{{ instance.home }}/keystore.p12"
password: "password"
# Neo4j configuration
neo4j:
enabled: true
uri: "neo4j://localhost:7687"
# For embedded database
home: "{{ instance.home }}/neo4j"
# Neo4j Projections
neo4jprojections:
enabled: true
projections:
- name: "examplesubscriber"
stream: "applications"
batchSize: 100
timeout: 30
# Domain Events
domainevents:
enabled: true
publisher:
enabled: true
# Reactive Streams
reactivestreams:
server:
enabled: true
client:
enabled: true
# OpenTelemetry
opentelemetry:
enabled: true
serviceName: "xorcery-example"
# Logging
log4j2:
Configuration:
status: INFO
Loggers:
Root:
level: INFO
Logger:
- name: com.exoreaction.xorcery.examples
level: DEBUGConfiguration values can be overridden with environment variables:
INSTANCE_NAME- Instance nameINSTANCE_ENVIRONMENT- Environment (dev, test, prod)JETTY_SERVER_HTTP_PORT- HTTP portJETTY_SERVER_SSL_PORT- HTTPS portNEO4J_URI- Neo4j connection URIEVENTSTORE_HOST- EventStore hostOPENSEARCH_HOST- OpenSearch host
| Technology | Purpose | Version |
|---|---|---|
| Xorcery | Microservices Framework | 0.166.9 |
| Java | Programming Language | 21+ |
| Maven | Build Tool | 3.8+ |
| HK2 | Dependency Injection | 3.1.1 |
| Jersey | JAX-RS REST API | 3.1.11 |
| Jetty | HTTP/WebSocket Server | 12.1.1 |
| Project Reactor | Reactive Streams | 3.7.11 |
| Neo4j | Graph Database | 5.28.5 |
| Log4j | Logging | 2.25.2 |
| Jackson | JSON Processing | 2.20.0 |
| Thymeleaf | Template Engine | 3.1.2 |
| JUnit | Testing | 6.0.0 |
| OpenTelemetry | Observability | 1.54.1 |
These examples have been migrated from earlier versions of Xorcery to 0.166.9, which includes several breaking changes.
All Maven dependencies must be updated:
<!-- OLD (pre-0.166.9) -->
<dependency>
<groupId>com.exoreaction.xorcery</groupId>
<artifactId>xorcery-core</artifactId>
</dependency>
<!-- NEW (0.166.9+) -->
<dependency>
<groupId>dev.xorcery</groupId>
<artifactId>xorcery-core</artifactId>
</dependency>All Java imports must be updated:
// OLD (pre-0.166.9)
import com.exoreaction.xorcery.configuration.Configuration;
import com.exoreaction.xorcery.core.Xorcery;
// NEW (0.166.9+)
import dev.xorcery.configuration.Configuration;
import dev.xorcery.core.Xorcery;Find and replace in your IDE:
- Find:
com.exoreaction.xorcery - Replace:
dev.xorcery
The biggest architectural change is the replacement of persistent subscribers with Neo4j projections:
@Service(name = "mysubscriber")
public class MySubscriber extends BasePersistentSubscriber {
public MySubscriber(Configuration configuration) {
super(configuration);
}
@Override
public boolean filter(MetadataJsonNode<ArrayNode> event) {
// Filter events
return true;
}
@Override
public void handle(MetadataJsonNode<ArrayNode> eventsWithMetadata) {
// Process events
}
}@Service(name = "mysubscriber")
public class MySubscriber implements Neo4jEventProjection {
private static final Logger logger = LogManager.getLogger(MySubscriber.class);
@Override
public void write(MetadataEvents events, Transaction transaction) throws Throwable {
// Filter and process events
events.data().stream()
.filter(this::shouldProcess)
.forEach(event -> handleEvent(event, transaction));
}
private boolean shouldProcess(DomainEvent event) {
if (event instanceof JsonDomainEvent jde) {
JsonNode json = jde.json();
// Filter logic
return true;
}
return false;
}
private void handleEvent(DomainEvent event, Transaction transaction) {
if (event instanceof JsonDomainEvent jde) {
JsonNode json = jde.json();
// Write to Neo4j using transaction
String cypher = """
MERGE (n:MyNode {id: $id})
SET n.data = $data
""";
transaction.execute(cypher, Map.of(
"id", json.get("id").asText(),
"data", json.get("data").asText()
));
}
}
}Key Differences:
- ✅ Implements
Neo4jEventProjectioninstead of extendingBasePersistentSubscriber - ✅ Receives a Neo4j
Transactionfor direct database access - ✅ Works with
MetadataEventscontainingList<DomainEvent> - ✅ Provides transactional guarantees
- ✅ Allows complex graph operations
Dependencies Change:
<!-- OLD -->
<dependency>
<groupId>com.exoreaction.xorcery</groupId>
<artifactId>xorcery-reactivestreams-persistentsubscriber</artifactId>
</dependency>
<!-- NEW -->
<dependency>
<groupId>dev.xorcery</groupId>
<artifactId>xorcery-neo4j-projections</artifactId>
</dependency>
<dependency>
<groupId>dev.xorcery</groupId>
<artifactId>xorcery-neo4j-embedded</artifactId>
</dependency>The client/server API signatures have changed:
// OLD
serverWebSocketStreams.publisher("mystream", JsonNode.class, publisher);
// NEW
serverWebSocketStreams.publisher("mystream", ServerWebSocketOptions.instance(), JsonNode.class, publisher);// OLD
clientWebSocketStreams.subscribe(ClientWebSocketOptions.instance(), JsonNode.class)
.contextWrite(Context.of(ClientWebSocketStreamContext.serverUri, uri))
// NEW
URI serverUri = URI.create("wss://localhost:8443/streams/mystream");
clientWebSocketStreams.subscribe(serverUri, ClientWebSocketOptions.instance(), JsonNode.class)Module names remain consistent, but ensure correct module references:
open module xorcery.examples.myapp {
exports com.exoreaction.xorcery.examples.myapp;
// Core modules
requires xorcery.core;
requires xorcery.configuration.api;
// Domain Events
requires xorcery.domainevents.api;
// Neo4j Projections (NEW)
requires xorcery.neo4j.projections;
requires xorcery.neo4j.shaded;
// Reactive Streams
requires xorcery.reactivestreams.api;
requires xorcery.reactivestreams.client;
requires xorcery.reactivestreams.server;
// OpenTelemetry
requires xorcery.opentelemetry.sdk;
// Dependency Injection
requires org.glassfish.hk2.api;
// Logging
requires org.apache.logging.log4j;
// JSON
requires com.fasterxml.jackson.databind;
// Testing
requires static xorcery.junit;
requires static org.junit.jupiter.api;
}Neo4j projections configuration:
# OLD (persistent subscriber)
persistentsubscriber:
subscriptions:
- name: "mysubscriber"
stream: "mystream"
# NEW (Neo4j projections)
neo4jprojections:
enabled: true
projections:
- name: "mysubscriber"
stream: "mystream"
batchSize: 100
timeout: 30- Update all Maven dependencies from
com.exoreaction.xorcerytodev.xorcery - Update all Java imports from
com.exoreaction.xorcery.*todev.xorcery.* - Replace
BasePersistentSubscriberwithNeo4jEventProjection - Update reactive streams client/server API calls
- Update
module-info.javato requirexorcery.neo4j.projections - Update configuration from
persistentsubscribertoneo4jprojections - Update
ServerWebSocketStreams.publisher()calls to includeServerWebSocketOptions - Update
ClientWebSocketStreams.subscribe()calls to include explicit URI - Clean and rebuild:
mvn clean install - Run all tests:
mvn test
If you get SSL certificate errors when accessing https://localhost:8443:
# Use --insecure flag with curl
curl --insecure https://localhost:8443/api/greeter
# Or import the certificate into your trust store
# Certificate is at: instance.home/keystore.p12For browsers: Accept the self-signed certificate warning or import the certificate.
If the default ports are already in use:
# Override in xorcery.yaml
jetty.server:
http:
port: 8081 # Change to available port
ssl:
port: 9443 # Change to available portOr use environment variables:
export JETTY_SERVER_HTTP_PORT=8081
export JETTY_SERVER_SSL_PORT=9443If you get "module not found" errors during compilation:
- Check groupId: Ensure all Xorcery dependencies use
dev.xorcery - Check module-info.java: Ensure correct module names (e.g.,
xorcery.core, notcom.exoreaction.xorcery.core) - Clean build:
mvn clean install
- Check POM: Ensure dependency is in the POM and matches module-info requirement
If Neo4j fails to start:
# Check disk space
df -h
# Stop any running Neo4j instances
ps aux | grep neo4j
kill <pid>
# Delete database directory (WARNING: deletes all data)
rm -rf instance.home/neo4j/data
# Restart application
mvn clean install
java -jar target/your-app.jarWhen using EventStore:
# Ensure EventStore is running
docker-compose up -d xe-eventstore
# Check logs
docker-compose logs -f xe-eventstore
# Check if port is accessible
curl http://localhost:2113
# Access UI
open http://localhost:2113# Ensure OpenSearch is running
docker-compose up -d xe-opensearch
# Check logs
docker-compose logs -f xe-opensearch
# Check if accessible
curl http://localhost:9200
# Access dashboard
open http://localhost:5601If tests don't run in IntelliJ but work with Maven:
Problem: IntelliJ has known issues with JPMS (Java Platform Module System) test support, especially with complex module dependencies.
Solution: Run tests via Maven instead:
# Run all tests
mvn test
# Run specific test
mvn test -Dtest=GreeterResourceTest
# Run tests for specific module
mvn test -pl xorcery-examples-greeterNote: This doesn't affect CI/CD builds (Jenkins), which use Maven.
Alternative: Configure IntelliJ Run Configuration:
- Run → Edit Configurations
- Add VM options:
--add-reads xorcery.examples.myapp=ALL-UNNAMED --add-opens xorcery.examples.myapp/com.exoreaction.xorcery.examples.myapp.test=ALL-UNNAMED
If you get compilation errors after updating to 0.166.9:
# Clean everything
mvn clean
# Delete IDE files (IntelliJ)
rm -rf .idea/
rm *.iml
# Delete Maven target directories
find . -type d -name target -exec rm -rf {} +
# Reimport project in IDE
# Then rebuild
mvn clean installIf you get OutOfMemoryError during build:
# Increase Maven memory
export MAVEN_OPTS="-Xmx2048m -XX:MaxPermSize=512m"
# Or set in .mavenrc file
echo "MAVEN_OPTS='-Xmx2048m -XX:MaxPermSize=512m'" > ~/.mavenrc
# Then rebuild
mvn clean install- GitHub: https://github.com/Cantara/xorcery
- Documentation: Check the README and Wiki in the main repository
- Issues: https://github.com/Cantara/xorcery/issues
Several PDF guides are available in the docs/getting-started/ directory:
- Xorcery is born - Build high-performance microservices
- Xorcery Examples - Forum Application walkthrough
- Xorcery Samples - Greeter example explained
- Xorcery implements Reactive Streams - Streaming architecture
- Xorcery uses Jetty & Jersey - mTLS and JWT implementation
- Jersey (JAX-RS): https://eclipse-ee4j.github.io/jersey/
- Jetty (Server): https://eclipse.dev/jetty/
- Project Reactor: https://projectreactor.io/
- Neo4j: https://neo4j.com/docs/
- HK2 (DI): https://javaee.github.io/hk2/
- OpenTelemetry: https://opentelemetry.io/docs/
- Jackson (JSON): https://github.com/FasterXML/jackson
Contributions are welcome! Please follow these guidelines:
-
Fork the repository
git clone https://github.com/YOUR_USERNAME/xorcery-examples.git
-
Create a feature branch
git checkout -b feature/amazing-feature
-
Make your changes
- Follow existing code style
- Add tests for new features
- Update documentation as needed
-
Test your changes
mvn clean test -
Commit your changes
git commit -m 'Add amazing feature' -
Push to your fork
git push origin feature/amazing-feature
-
Open a Pull Request
- Describe your changes
- Link to related issues
- Wait for review
- ✅ Follow existing code style and formatting
- ✅ Add tests for new features (aim for 80%+ coverage)
- ✅ Update documentation (README, JavaDoc, etc.)
- ✅ Ensure all tests pass:
mvn clean test - ✅ Keep examples simple and focused on one concept
- ✅ Use
dev.xorcerygroupId for all Xorcery dependencies - ✅ Follow Java 21+ best practices
- ✅ Use meaningful commit messages
- ✅ Update CHANGELOG.md if adding new features
- Indentation: 4 spaces (no tabs)
- Line length: Max 120 characters
- Braces: K&R style
- Naming:
- Classes:
PascalCase - Methods/variables:
camelCase - Constants:
UPPER_SNAKE_CASE
- Classes:
- JavaDoc: Required for public APIs
- Write unit tests for business logic
- Write integration tests for end-to-end scenarios
- Use meaningful test names:
shouldDoSomethingWhenCondition() - Use JUnit 5 features (parameterized tests, nested tests, etc.)
- Mock external dependencies
This examples repository uses:
- Xorcery: 0.166.9
- Java: 21+
- Maven: 3.8+
| Version | Xorcery | Java | Key Changes |
|---|---|---|---|
| 1.166.1 | 0.166.9 | 21+ | Migrated to dev.xorcery groupId, Neo4j projections |
| 1.126.7 | 0.126.x | 17+ | Original version with persistent subscribers |
- ✅ Updated all dependencies from
com.exoreaction.xorcerytodev.xorcery - ✅ Updated all package imports to
dev.xorcery.* - ✅ Migrated persistent subscriber example to Neo4j projections API
- ✅ Updated reactive streams client/server API usage
- ✅ Fixed all module-info.java declarations