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.
(req, res, next) => {}/users/:id, regex paths, nested routersai().rag(question) - retrieval, generation, done# 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, /health/live, /health/readyRAG requires a running Qdrant instance and an embedding model API key.
docker compose up -d vaultvault.url=http://localhost:8200
vault.token=root
vault.kvMount=secret
In 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>.
1) Start Qdrant (docker-compose already includes it):
docker compose up -d qdrant
2) Configure environment:
export QDRANT_URL=http://localhost:6333
export OPENAI_API_KEY=your-api-key
3) 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 | ⚠️ Slow start | ✅ Fast start |
roya new, roya dev)We’re just getting started and would love your help!
See CONTRIBUTING.md for detailed guidelines. (Coming soon)
Roya stands on the shoulders of giants:
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:
Built with ❤️ by developers who love Express but need Java’s power.
Let’s make Java web development fun again.
Last updated: January 2025