Express for Java. AI-Native. 100x Faster.
Roya brings Express.js's beloved API to modern Java, with built-in AI superpowers and cloud-cost optimization.
import static com.akilisha.oss.roya.Roya.*;
void main() {
var app = create();
app.use(cors());
app.use(json());
app.get("/", (req, res, next) -> {
res.send("Hello World");
});
app.get("/users/:id", (req, res, next) -> {
res.json(Map.of("id", req.params().get("id")));
});
app.listen(3000);
}If you know Express, you already know Roya.
- 90% code compatibility for Express.js migration
- Same middleware pattern:
(req, res, next) => {} - Identical routing:
/users/:id, regex paths, nested routers - Feels like Express, but with Java's type safety
- Virtual threads: 1M+ concurrent connections (vs Express's ~10K)
- 10x lower memory: 50MB baseline (vs Express's 400MB)
- 5x faster requests: p99 latency <20ms
- FFM API: Zero-copy I/O matching nginx performance
- Built-in LLM integration: OpenAI, Anthropic, Cohere, local models
- RAG in one line:
ai().rag(question)- retrieval, generation, done - AI Agents: Structured concurrency makes multi-agent workflows trivial
- Vector search: Automatic embedding and semantic search
- Cost tracking: Built-in token counting and budget alerts
- 70-80% lower cloud costs vs Spring Boot or Express at scale
- $1.5-2M annual savings for typical enterprise (based on $200K/month current spend)
- Intelligent caching: Semantic similarity for LLM responses
- Auto-scaling friendly: Fast startup (<100ms), small memory footprint
- Java 21+ (LTS)
- That's it!
# CLI tool (planned)
$ roya new my-app
$ cd my-app
$ roya dev// Main.java
import static com.roya.Roya.*;
void main() {
var app = create();
app.get("/", (req, res, next) -> {
res.send("Hello from Roya!");
});
app.listen(3000, () -> {
System.out.println("Server running on http://localhost:3000");
});
}import static com.roya.Roya.*;
record User(int id, String name, String email) {}
void main() {
var app = create();
app.use(cors());
app.use(json());
// Get all users
app.get("/users", (req, res, next) -> {
var users = db().query("SELECT * FROM users").list(User.class);
res.json(users);
});
// Get user by ID
app.get("/users/:id", (req, res, next) -> {
var user = db().findOne(User.class, req.params().get("id"));
if (user == null) {
res.status(404).json(Map.of("error", "User not found"));
} else {
res.json(user);
}
});
// Create user
app.post("/users", (req, res, next) -> {
var user = req.body(User.class);
db().insert(user);
res.status(201).json(user);
});
app.listen(3000);
}import static com.roya.Roya.*;
void main() {
var app = create();
app.plugin(openai()); // Auto-configured from OPENAI_API_KEY
// Simple chat
app.post("/chat", (req, res, next) -> {
var answer = ai.llm().ask("You are a helpful assistant", req.body().message());
res.json(Map.of("answer", answer));
});
// Structured extraction
record Product(String name, BigDecimal price, List<String> features) {}
app.post("/extract", (req, res, next) -> {
var product = ai.llm().extract(Product.class, req.body().description());
res.json(product); // Guaranteed valid Product
});
// RAG (Retrieval-Augmented Generation)
app.post("/ask", (req, res, next) -> {
var rag = ai.ragApi().ask(req.body().question());
res.json(Map.of("answer", rag.answer()));
});
app.listen(3000);
}Presign a GET/PUT URL and upload large files via multipart using the example server ObjectStorageDemo (port 3003):
docker compose up -d minio
./gradlew :roya-examples:run --args="ObjectStorageDemo"
# Presigned PUT URL (JSON body)
curl -s localhost:3003/storage/presign/put \
-H 'Content-Type: application/json' \
-d '{"bucket":"media","key":"big.bin","ttlSeconds":600,"contentType":"application/octet-stream"}'
# Presigned GET URL
curl -s "localhost:3003/storage/presign/get?bucket=media&key=big.bin&ttlSeconds=600"
# Multipart upload (base64 body for demo simplicity)
CONTENT_B64=$(echo -n "$(head -c 10485760 /dev/zero | tr '\0' 'A')" | base64) # ~10MB of 'A'
curl -s localhost:3003/storage/multipart/put \
-H 'Content-Type: application/json' \
-d "{\"bucket\":\"media\",\"key\":\"big.bin\",\"contentBase64\":\"${CONTENT_B64}\",\"contentType\":\"application/octet-stream\",\"partSizeMb\":5}"WebSocket registration:
app.ws("/ws/echo", new io.helidon.websocket.WsListener() {
@Override public void onOpen(io.helidon.websocket.WsSession s) { s.send("connected"); }
@Override public void onMessage(io.helidon.websocket.WsSession s, String text, boolean last) { s.send("echo: " + text); }
});At listen(), Roya installs collected WebSocket endpoints using Helidonβs WsRouting. See Helidon docs for details: https://helidon.io/docs/v4/se/websocket
Enable JSON logs for request tracing (Logstash-compatible) using Morgan:
app.use(Morgan.builder()
.format(Morgan.Format.COMBINED)
.structured(true)
.captureRequestIdHeader("X-Request-Id")
.redactHeaders(Set.of("authorization","cookie","set-cookie"))
.build());Fields include: http.method, path, status, duration_ms, remote.ip, request_id, trace/span IDs, and redacted headers. Logs are emitted to stdout for Docker log collectors.
Protect endpoints from abuse with IP-based rate limiting:
import com.akilisha.oss.roya.core.middleware.RateLimit;
import java.time.Duration;
// Default: 100 requests per 15 minutes per IP
app.use(RateLimit.rateLimit());
// Custom limit
app.use(RateLimit.builder()
.max(200)
.window(Duration.ofMinutes(1))
.build());
// Custom key function (e.g., rate limit by user ID)
app.use(RateLimit.builder()
.key(req -> req.get("user").userId())
.max(50)
.window(Duration.ofSeconds(60))
.build());When the limit is exceeded, requests receive 429 Too Many Requests with Retry-After header and X-RateLimit-* headers.
Quick commands to boost dev ergonomics:
# Help
./gradlew :roya-cli:run --args="--help"
# Scaffold a minimal Roya app
./gradlew :roya-cli:run --args="new my-app --group com.acme"
# Run an example (same as :roya-examples:run with args)
./gradlew :roya-cli:run --args="dev HelloWorld"
# Run any module/class
./gradlew :roya-cli:run --args="run --class com.akilisha.oss.roya.examples.ObjectStorageDemo --module :roya-examples --args \"ObjectStorageDemo\""
# Docker helpers (qdrant|minio|postgres|vault)
./gradlew :roya-cli:run --args="compose up --service qdrant"
./gradlew :roya-cli:run --args="compose down --service qdrant"
# Dry run (print commands without executing)
./gradlew :roya-cli:run --args="--dry-run dev HelloWorld"Roya enables Helidon Health/Tracing when present on the classpath:
- Health endpoints:
/health,/health/live,/health/ready - Tracing backend via env/props (Zipkin/OTEL)
RAG requires a running Qdrant instance and an embedding model API key.
- Start Vault (dev):
docker compose up -d vault - Configure app to use Vault-backed secrets via Helidon Config keys:
vault.url=http://localhost:8200
vault.token=root
vault.kvMount=secretIn code, SecretsMiddleware.defaults() registers Secrets into services. It auto-detects Vault config and reads from KV v2 (/v1/{mount}/data/{path}), falling back to config-backed secrets at secrets.<path>.<key>.
- Start Qdrant (docker-compose already includes it):
docker compose up -d qdrant- Configure environment:
export QDRANT_URL=http://localhost:6333
export OPENAI_API_KEY=your-api-key- Use the unified AI APIs:
ai.vectors().indexPath("kb", Path.of("docs/"), AI.ChunkingOptions.fixed(800, 200));
var rag = ai.ragApi().ask("How do I configure caching?");import static com.roya.Roya.*;
void main() {
var app = create();
app.use(cors());
app.use(json());
// Logging middleware
app.use((req, res, next) -> {
System.out.printf("%s %s%n", req.method(), req.path());
next.handle(req, res);
});
// Auth middleware
Handler auth = (req, res, next) -> {
var token = req.headers().get("Authorization");
if (token.isEmpty()) {
res.status(401).json(Map.of("error", "Unauthorized"));
return;
}
var user = verifyToken(token.get());
req.set(CURRENT_USER, user);
next.handle(req, res);
};
// Public endpoint
app.get("/", (req, res, next) -> {
res.json(Map.of("message", "Public"));
});
// Protected endpoint
app.get("/profile", auth, (req, res, next) -> {
var user = req.get(CURRENT_USER);
res.json(user);
});
app.listen(3000);
}import static com.roya.Roya.*;
void main() {
var app = create();
// API router
var apiRouter = Router.create();
apiRouter.get("/status", (req, res, next) -> {
res.json(Map.of("status", "ok"));
});
// Users router
var usersRouter = Router.create();
usersRouter.get("/", (req, res, next) -> res.json(getAllUsers()));
usersRouter.get("/:id", (req, res, next) -> res.json(getUser(req.params().get("id"))));
usersRouter.post("/", (req, res, next) -> res.status(201).json(createUser(req.body())));
// Mount routers
apiRouter.use("/users", usersRouter);
app.use("/api", apiRouter);
// Result: /api/status, /api/users, /api/users/:id
app.listen(3000);
}| Feature | Express | Roya |
|---|---|---|
| API Similarity | - | β 90% compatible |
| Lines of Code | 100 | 100 |
| Type Safety | β Runtime errors | β Compile-time safety |
| Concurrent Connections | ~10K | 1M+ |
| Memory Usage | 400MB | 50MB |
| Requests/Second | 5K | 50K |
| Built-in AI | β | β LLM, RAG, Agents |
| Cost (AWS, 10K RPS) | $730/mo | $73/mo |
| Feature | Spring Boot | Roya |
|---|---|---|
| Learning Curve | Steep (annotations, magic) | Gentle (Express-like) |
| Startup Time | 2-10 seconds | <100ms |
| Memory Baseline | 250MB | 50MB |
| Code Verbosity | High | Low |
| AI Integration | Bolt-on (Spring AI) | Native |
| Serverless-Ready | β Fast start |
- Architecture design
- Whitepaper
- API specification
- HTTP server (Helidon NΓma base)
- Express-compatible routing
- Middleware pipeline
- Request/Response API
- Basic plugins (CORS, JSON)
- Performance benchmarks
- Complete HTTP/1.1 + HTTP/2
- Database plugin (JOOQ)
- AI plugin (OpenAI)
- Vector store integration
- RAG implementation
- CLI tool (
roya new,roya dev) - Documentation site
- Auth plugins (JWT, OAuth2)
- More AI providers (Anthropic, Cohere, local)
- Caching, messaging, templates
- Testing framework
- Migration tools (Express β Roya)
- IntelliJ/VS Code plugins
- GraalVM native compilation
- Kubernetes operators
- Enterprise support
- Managed cloud platform
- Advanced AI features
- Everything is middleware - Handlers, routers, apps all implement the same interface
- Request-scoped by default - Services injected per-request via scoped values
- Virtual threads everywhere - No thread pools, no async complexity
- AI as a first-class citizen - Not bolted on, built in
- Express compatibility - Same mental model, same API patterns
- HTTP Server: Helidon NΓma (virtual thread-native)
- Concurrency: Virtual threads (Project Loom)
- Memory: Foreign Function & Memory API (zero-copy I/O)
- Context: Scoped Values (request context propagation)
- Agents: Structured Concurrency (multi-agent orchestration)
- Database: JOOQ (type-safe SQL)
- AI: Custom clients (OpenAI, Anthropic, Cohere)
- Vectors: Qdrant, Pinecone, embedded options
We're just getting started and would love your help!
- Star this repo β - Help us gain visibility
- Join discussions - Share ideas, use cases, feedback
- Code contributions - Once prototype is ready (Q1 2025)
- Documentation - Examples, tutorials, guides
- Spread the word - Tweet, blog, talk about Roya
- HTTP server implementation (Java + Helidon NΓma)
- Routing engine (path matching, regex support)
- Plugin system (architecture and core plugins)
- AI integrations (LLM clients, RAG, agents)
- Documentation (guides, examples, API reference)
- Testing (framework testing utilities)
- DevOps (CI/CD, Docker, K8s)
See CONTRIBUTING.md for detailed guidelines. (Coming soon)
- GitHub Discussions: Ask questions, share ideas
- Discord (Coming soon): Real-time chat with the community
- Twitter: @RoyaFramework (Coming soon)
- Blog: Technical deep-dives and updates (Coming soon)
Roya stands on the shoulders of giants:
- Express.js - For proving that simplicity wins
- Project Loom - For making lightweight concurrency real
- Helidon - For building on modern Java primitives
- JOOQ - For showing SQL can be type-safe and elegant
We're not reinventing the wheel. We're building the wheel that 2025 needs.
Roya (Ψ±ΩΫΨ§) is Persian for "dream" - fitting for an ambitious vision to unite Express's elegance with Java's power.
Not yet. We're in the design/prototype phase (Q1 2025). Follow the repo for updates.
Migration guide coming in Q2 2025. Expect 90% code compatibility - mostly syntax changes (const β var, arrow functions, etc.)
Migration path coming in Q2 2025. Focus will be on greenfield microservices first, then gradual migration strategies.
Yes, planned for Phase 4 (2026). Fast startup + small binary = perfect for serverless.
Absolutely. Roya is designed for modern JVM languages. Kotlin's concise syntax will be a great fit.
Yes. Roya is just Java. Any JVM library works. JDBC, Jackson, etc. all compatible.
Built-in database plugin (JOOQ-based) manages connection pooling, virtual thread integration, and provides a clean query API.
Planned for Phase 2. Simple API: db().transaction(tx -> { ... })
Planned for Phase 2. Express-compatible API: app.ws('/chat', handler)
Phase 1: OpenAI
Phase 2: Anthropic, Cohere
Phase 3: Local models (Ollama, llama.cpp), Azure OpenAI, AWS Bedrock
Free and open source (Apache 2.0 license). Commercial support and managed hosting coming later.
Apache License 2.0
Roya is free and open source. Use it in commercial projects, modify it, distribute it. Just give credit and don't sue us. π
See LICENSE for full details.
Current Status: π‘ Design & Architecture Complete
Next Milestone: π΅ Prototype (Q1 2025)
Looking for: Contributors, feedback, early adopters, sponsors
If you believe in this vision:
- β Star this repo - It helps with visibility
- π¦ Tweet about it - Spread the word
- π¬ Join discussions - Your feedback shapes the project
- πΌ Sponsor development - Help us build faster (Contact us)
- π€ Contribute code - Join the core team
Built with β€οΈ by developers who love Express but need Java's power.
Let's make Java web development fun again.
Last updated: January 2025