v3.3.1 adds a smaller application bootstrap while preserving explicit Java business composition.
Normal applications select one named module with RestApplication.run(...); the existing builder
remains available for advanced lifecycle customization.
- Use
RestApplication.run(Module...)for the minimal lifecycle. - Use
RestApplication.runStandard(Module...)when the property-controlled production feature set is required. - Keep handlers and managed resources in a named module instead of nesting builder code in
main. - Existing REST annotations, handlers, services, and builder startup remain compatible.
- Native ABI and packaged DLL/SO files are unchanged.
<dependency>
<groupId>com.reactor</groupId>
<artifactId>rust-java-rest</artifactId>
<version>3.3.1</version>
</dependency>Full release notes:
v3.2.5 is a stable patch release for the current native runtime package used by the REST framework,
java-rust-cache:0.2.1, and java-rust-dubbo:0.2.0. The Java programming model is unchanged:
handlers, services, components, records, database code, and business logic stay in Java.
- Maven dependency version is now
3.2.5. - Native resources are refreshed for
java-rust-dubbo:0.2.0native response handle usage. - Redis native ABI version
3remains available forjava-rust-cache:0.2.1Cluster and Sentinel scenarios. - Existing REST annotations and Java handler/service code remain source-compatible.
- Production artifact rules from the
3.2.xline still apply: normal dependency for applications, sample classifier only for demos/benchmarks.
<dependency>
<groupId>com.reactor</groupId>
<artifactId>rust-java-rest</artifactId>
<version>3.2.5</version>
</dependency>Use rust-java-rest:3.2.5 with java-rust-dubbo:0.2.0 when the consumer uses native response
handles. Do not republish or reuse the old 3.2.4 package with changed native binaries.
Full release notes:
v3.2.2 is a stable patch release for production diagnostics, low-RSS evidence, and safer heavy
JSON tuning. The Java programming model is unchanged: handlers, services, components, records,
database code, and business logic stay in Java.
- Maven dependency version is now
3.2.2. - Route diagnostics separate production routes from benchmark-only comparison routes.
- Heavy JSON diagnostics correctly recognize direct primitive output writers.
JsonBodyProducercan be returned directly for default200 OKJSON producer routes.- Optional route-local JNI admission is available for explicitly gated hot routes.
- Minimal production benchmark images now generate startup indexes, avoiding classpath-scan noise.
- Anonymous memory evidence reports heap, JIT/code, class metadata, direct buffers, Rust-accounted memory, thread stack budget, and residual anon.
- Conservative idle native trim is validated as opt-in reclaim for low-traffic idle pods.
<dependency>
<groupId>com.reactor</groupId>
<artifactId>rust-java-rest</artifactId>
<version>3.2.2</version>
</dependency>| Signal | Result |
|---|---|
| Production heavy JSON object-graph routes | 0 |
| Benchmark-only heavy JSON object-graph routes | 1 |
| Conservative trim current delta | -17.543 MiB |
| Conservative trim anon delta | -18.082 MiB |
| Conservative trim residual-anon delta | -13.968 MiB |
Read this as production evidence, not a marketing claim. micro-rest-plus is a controlled-overload
profile: raw/precomputed JSON is strong through c512, while hot heavy JSON still needs producer,
direct, raw, native response paths and route budgets.
Full release notes:
v3.2.1 is a stable patch release for production packaging and runtime hardening.
The Java programming model is unchanged. Your handlers, services, components, request records, response records, DB code, and business logic stay in Java. Rust continues to handle the HTTP I/O plane, bounded native memory, file streaming, WebSocket transport, and selected serialization-heavy paths.
- Maven dependency version is now
3.2.1. - Production-like benchmark images can use
rust-java-rest-*-core-runtime.jarinstead of frameworktarget/classes. - Default jar,
core-runtime, sources jar, and javadocs exclude framework sample/benchmark packages. sampleclassifier remains available for demos and benchmark examples only.- Runtime profiles no longer overwrite values explicitly configured in
rust-spring.properties. - Benchmark/demo comparison routes can now be marked
@BenchmarkOnlyRoute; diagnostics separate production route counts from sample comparison routes. - README, benchmark docs, and production runtime docs now explain the production artifact rule.
<dependency>
<groupId>com.reactor</groupId>
<artifactId>rust-java-rest</artifactId>
<version>3.2.1</version>
</dependency>Use the normal Maven dependency for applications. Use rust-java-rest-3.2.1-core-runtime.jar only
when a benchmark/container classpath needs one lean framework runtime jar. Do not use
rust-java-rest-3.2.1-sample.jar in production; it intentionally contains demo handlers, DTOs,
benchmark endpoints, and a sample startup index.
The existing 3.2.1 package already follows this rule:
| Artifact | Contains framework sample/benchmark packages? | Intended use |
|---|---|---|
rust-java-rest-3.2.1.jar |
No | Normal Maven dependency |
rust-java-rest-3.2.1-core-runtime.jar |
No | Lean framework runtime classpath |
rust-java-rest-3.2.1-sample.jar |
Yes | Demo and bundled benchmark app only |
Do not overwrite a published Maven package under the same version. If packaged code, native binaries, or benchmark fixtures need to change for consumers, publish a new patch version. Documentation and release text can be clarified without changing package bytes.
For production-like RSS reporting, prefer the minimal production benchmark mode:
powershell -ExecutionPolicy Bypass -File .\benchmark\linux_smaps_breakdown.ps1 `
-AppMode minimal `
-RuntimeProfile micro-rest `
-ConcurrencyValues 64,256 `
-DurationSeconds 4 `
-IdleSeconds 3 `
-FinalIdleSeconds 6Use the bundled sample app only when you are intentionally testing bundled demo endpoints.
openj9-micro-rss-jitcap.options is available as an experiment for memory-sensitive services. It
adds -Xcodecachetotal8m, which caps OpenJ9 JIT code-cache commitment while keeping JIT enabled.
Full local gate result, micro-rest, c64/c256/c512, sample repeat 3, minimal smaps repeat 3:
| Gate | Result |
|---|---|
| Optional JIT-cap usable for the common endpoint set | FAIL |
| Default profile candidate | FAIL |
| Minimal production RSS gain | 5.952 MiB |
| p99 regression failures | 4 |
| Legacy dynamic DTO c256/c512 regressions | 2 |
Read this as a production gate, not as a marketing number. The option reduced minimal production
RSS, but it is not approved as a common default because mixed Java business workloads can regress at
p99. Use it only after your own endpoint matrix passes; for hot heavy JSON, prefer
JsonProducerResponse or direct writer before lowering the JIT code-cache budget.
Current branch note: the bundled benchmark app now separates optimized DTO-shaped heavy JSON from
the legacy Java DTO graph. /api/v1/heavy/dto uses JsonProducerResponse; /api/v1/heavy/dto/legacy
keeps the real object graph path for comparison.
mvn -q testmvn -q -DskipTests package- Jar policy check for main jar,
core-runtime, and sources jar. - Native-static Dubbo consumer smoke benchmark with
-FrameworkArtifactMode core-runtime.
This stable release makes the low-RSS path easier to use and easier to measure. The normal Java programming model is unchanged: handlers, services, components, and DTO contracts stay in Java. Rust continues to handle HTTP I/O, native response paths, file streaming, overload control, and selected serialization-heavy fast paths.
Use this release when you want a bounded, low-overhead Java REST runtime with Rust handling the I/O plane. The strongest paths are small JSON, raw/precomputed JSON, direct/Rust JSON writers, native cache, and file responses. Endpoints that build large Java DTO graphs still work normally, but they should be tuned route by route when RSS or p99 is important.
- Immutable
FileResponseroutes can use@NativeStaticFileRoute; Rust serves runtime requests without calling the Java handler. - Small immutable files can be inlined in native memory with
reactor.rust.static-file.inline-max-bytes. - Larger immutable files remain disk-backed and are protected by
reactor.rust.static-file.max-concurrent-streams. - File stream chunk size is configurable with
reactor.rust.file-stream.chunk-bytes. - Benchmark reporting now separates echo raw/parse, small JSON legacy/direct, dynamic DTO, direct writer, Rust writer, raw JSON, native cache, static file, and large stream paths.
- Benchmark scripts can append JVM property overrides with
-FrameworkJavaOptsAppendfor repeatable profile experiments. - Native ABI is
19; use the Windows DLL and Linux SO shipped with this package. - The UTF-8 fixes from
v3.1.0-rc4remain in place for response bodies, path variables, request params, cookies, middleware query helpers, and WebSocket params.
| Use case | Use this first | Move to this when needed | Main effect |
|---|---|---|---|
| Small JSON | Java record DTOs | @DirectQuery* / @DirectPath* binding for hot scalar params |
Simple default for normal REST APIs |
| Dynamic DTO | Java record graph + DSL-JSON | Direct writer/raw/cache only for measured hot routes | Keeps business code clear |
| Raw/precomputed JSON | RawResponse.json(...) |
RawResponse.registeredJson(...) for immutable/read-heavy payloads |
Skips DTO serialization |
| Native cache JSON | Explicit bounded native cache key | @NativeStaticRoute only for immutable routes |
Avoids repeated body build and Java-to-Rust body transfer on hits |
| Direct JSON writer | Java record DTO | JsonBufferWriter or DirectJsonWriterRegistry |
Reduces DTO graph and serializer buffer allocation |
| Large file/export | FileResponse |
@NativeStaticFileRoute for immutable files |
Keeps file bytes out of Java heap |
| WebSocket push | WebSocketSession API |
Tune bounded outbound queues | Keeps push overload controlled |
Practical selection rule:
- Use Small JSON or Dynamic DTO for ordinary business endpoints.
- Use Raw/precomputed JSON when JSON bytes already exist before the handler returns.
- Use Native cache JSON only when the same response is intentionally reused with a clear key, TTL, or immutable route.
- Use Direct JSON writer only for hot fixed-shape JSON where benchmark data shows DTO/serialization cost.
| Profile | Pick it when | Trade-off |
|---|---|---|
low-rss |
Memory is the priority and controlled 503 is acceptable under overload |
Lowest practical memory, stricter queues |
balanced |
External RPC/database calls or heavier Java handlers need smoother p99 | More headroom, more RSS |
throughput |
Dedicated high-throughput service with a larger memory budget | More retained buffers/workers |
micro-rss / ultra-low-rss |
Very small services or experiments | Too strict for high fanout downloads |
Suggested low-RSS starting point:
reactor.runtime.profile=low-rss
reactor.rust.http.max-request-body-bytes=1048576
reactor.rust.http.max-response-body-bytes=8388608
reactor.rust.http.max-inflight-response-bytes=16777216
reactor.rust.http.max-connections=1024
reactor.rust.file-stream.chunk-bytes=65536
reactor.rust.static-file.inline-max-bytes=524288
reactor.rust.static-file.max-concurrent-streams=64<dependency>
<groupId>com.reactor</groupId>
<artifactId>rust-java-rest</artifactId>
<version>3.1.0</version>
</dependency>The JAR embeds the runtime native binaries at:
native/windows-x64/rust_hyper.dllnative/linux-x64/librust_hyper.so- legacy Windows resource path
rust_hyper.dll
GitHub Release assets expose platform-explicit names:
rust_hyper-windows-x64.dlllibrust_hyper-linux-x64.so
mvn -q testmvn -q -DskipTests packagecargo test- Windows native DLL rebuild
- Linux native SO rebuild
- General benchmark:
current_full_20260531_090441, profilelow-rss, CPU2, Rust-Java memory96m, Spring Boot memory512m, concurrency64/256/512/1000, repeat1. - Large file stream matrix:
stream_matrix_{32,64,128,256}_20260531_085157, 8 MiB file, inline disabled, concurrency256/512/1000.
Latest c512/c1000 comparable endpoints:
| Endpoint | C | Rust-Java RPS | Spring Boot RPS | Ratio | Rust P99 | Spring P99 | Rust Max Mem | Spring Max Mem |
|---|---|---|---|---|---|---|---|---|
| candidates | 512 | 12,347 | 2,533 | 4.87x | 126ms | 694ms | 80 MiB | 392 MiB |
| candidates | 1000 | 13,897 | 1,204 | 11.54x | 289ms | 1.94s | 94 MiB | 310 MiB |
| echo parse | 512 | 16,896 | 3,844 | 4.39x | 98ms | 338ms | 90 MiB | 426 MiB |
| echo parse | 1000 | 10,970 | 3,904 | 2.81x | 768ms | 614ms | 92 MiB | 423 MiB |
| heavy100 raw | 512 | 11,517 | 5,761 | 2.00x | 142ms | 289ms | 91 MiB | 422 MiB |
| heavy100 raw | 1000 | 15,247 | 5,050 | 3.02x | 254ms | 517ms | 93 MiB | 444 MiB |
| heavy100 dynamic DTO | 512 | 2,812 | 2,193 | 1.28x | 309ms | 515ms | 92 MiB | 420 MiB |
| heavy100 dynamic DTO | 1000 | 8,750 | 2,488 | 3.52x | 526ms | 633ms | 76 MiB | 422 MiB |
8 MiB file, inline disabled:
| Max Streams | C | RPS | P99 | 503 Rate | RSS After | Max Mem |
|---|---|---|---|---|---|---|
| 32 | 512 | 2,922 | 860ms | 98.66% | 20 MiB | 82 MiB |
| 64 | 512 | 2,176 | 1.98s | 98.28% | 27 MiB | 94 MiB |
| 128 | 512 | 1,749 | 3.16s | 97.07% | 42 MiB | 84 MiB |
| 256 | 512 | 1,318 | 6.94s | 96.74% | 58 MiB | 94 MiB |
Interpretation: for memory-sensitive services, start with 32 or 64 max concurrent streams. Higher
values accept more file work but increase p99 and RSS. Returning 503 under overload is intentional
backpressure, not a failed benchmark.
See PERFORMANCE_GUIDE.md for profile trade-offs and route-level tuning guidance.
Note: v3.0.1 fixes native library updates. See v3.0.0 below for full feature list.
This release combines extreme performance optimization with a complete REST API feature set including WebSocket support, async handlers, and dependency injection.
<dependency>
<groupId>com.reactor</groupId>
<artifactId>rust-java-rest</artifactId>
<version>3.0.0</version>
</dependency>Add to your pom.xml or settings.xml:
<repositories>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/esasmer-dou/rust-java-rest</url>
</repository>
</repositories>repositories {
maven {
url = uri("https://maven.pkg.github.com/esasmer-dou/rust-java-rest")
}
}
dependencies {
implementation 'com.reactor:rust-java-rest:3.0.0'
}| Endpoint | v2.0.0 | v3.0.0 | Improvement |
|---|---|---|---|
| GET /health | 8-12ms | 5-8ms | 33-40% faster |
| POST /order/create | 8-15ms | 6-11ms | 25-35% faster |
| Concurrent (10 req) | 8-15ms | 4-6ms | 50% faster |
| Metric | v2.0.0 | v3.0.0 | Improvement |
|---|---|---|---|
| Container Memory | 27-35 MB | 26-29 MB | 15% less |
| JRE Size | 35 MB | ~25 MB | 28% smaller |
| Per-request Allocation | ~2 KB | ~0 bytes | 100% reduction |
| Feature | Description |
|---|---|
| REST API | Spring Boot-like annotations (@GetMapping, @PostMapping, etc.) |
| Dependency Injection | Zero-overhead DI (@Component, @Service, @Autowired) |
| WebSocket | Full WebSocket support with rooms and broadcasting |
| Async Handlers | CompletableFuture support with virtual threads |
| Static Files | Production-ready static file serving with caching |
| Zero-Copy | Direct ByteBuffer JNI, no intermediate allocations |
| Ultra-Low Memory | Docker container under 50MB |
@Component
@WebSocket("/ws/echo")
public class EchoWebSocketHandler {
@OnOpen
public void onOpen(WebSocketSession session) {
System.out.println("Session opened: " + session.getId());
session.sendText("{\"type\":\"connected\",\"sessionId\":" + session.getId() + "}");
}
@OnMessage
public void onMessage(WebSocketSession session, String message) {
// Echo back the message
session.sendText("{\"type\":\"echo\",\"message\":\"" + escapeJson(message) + "\"}");
}
@OnClose
public void onClose(WebSocketSession session) {
System.out.println("Session closed: " + session.getId());
}
@OnError
public void onError(WebSocketSession session, String error) {
System.err.println("Error: " + error);
}
}@Component
@WebSocket("/ws/chat/{roomId}")
public class ChatWebSocketHandler {
private final ConcurrentHashMap<String, Set<WebSocketSession>> rooms = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(WebSocketSession session) {
// Get path parameter
String roomId = session.getPathParams().get("roomId");
// Add to room
rooms.computeIfAbsent(roomId, k -> ConcurrentHashMap.newKeySet()).add(session);
// Broadcast join
broadcast(roomId, "{\"type\":\"join\",\"sessionId\":" + session.getId() + "}");
}
@OnMessage
public void onMessage(WebSocketSession session, String message) {
String roomId = session.getPathParams().get("roomId");
// Broadcast to room
broadcast(roomId, "{\"type\":\"message\",\"text\":\"" + escapeJson(message) + "\"}");
}
@OnClose
public void onClose(WebSocketSession session) {
String roomId = session.getPathParams().get("roomId");
// Remove from room
rooms.get(roomId).remove(session);
// Broadcast leave
broadcast(roomId, "{\"type\":\"leave\",\"sessionId\":" + session.getId() + "}");
}
private void broadcast(String roomId, String message) {
for (WebSocketSession s : rooms.get(roomId)) {
s.sendText(message);
}
}
}// Get broadcaster instance
WebSocketBroadcaster broadcaster = WebSocketBroadcaster.getInstance();
// Broadcast to all sessions
broadcaster.broadcast("{\"type\":\"notification\",\"text\":\"Hello all!\"}");
// Broadcast to specific room
broadcaster.broadcastToRoom("room1", "{\"type\":\"message\",\"text\":\"Hello room1!\"}");
// Broadcast excluding sender
broadcaster.broadcast(message, excludeSessionId);
broadcaster.broadcastToRoom("room1", message, senderSession);
// Broadcast binary data
broadcaster.broadcastBinary(data);
broadcaster.broadcastBinaryToRoom("room1", data);
// Broadcast with filter
broadcaster.broadcastFiltered(message, session -> {
return session.getPathParams().get("role").equals("admin");
});
// Room management
broadcaster.joinRoom(sessionId, "room1");
broadcaster.leaveRoom(sessionId, "room1");
broadcaster.getSessionsInRoom("room1");
broadcaster.getRoomNames();// Echo
const ws = new WebSocket('ws://localhost:8080/ws/echo');
ws.onopen = () => ws.send('Hello!');
ws.onmessage = (e) => console.log(e.data);
// Chat room
const chat = new WebSocket('ws://localhost:8080/ws/chat/room1');
chat.onopen = () => chat.send('Hi everyone!');
chat.onmessage = (e) => console.log(e.data);@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
@Autowired
private NotificationService notificationService;
// Async method returning CompletableFuture
public CompletableFuture<Order> createOrderAsync(OrderRequest request) {
return CompletableFuture.supplyAsync(() -> {
// This runs on a virtual thread (Java 21+)
Order order = new Order(generateId(), request);
// Process payment (blocking call)
paymentService.process(order);
return order;
});
}
}@RequestMapping("/order")
public class OrderHandler {
@Autowired
private OrderService orderService;
// Async handler - returns CompletableFuture
@PostMapping(value = "/create", requestType = OrderRequest.class, responseType = OrderResponse.class)
public CompletableFuture<ResponseEntity<OrderResponse>> createAsync(
@RequestBody OrderRequest request) {
return orderService.createOrderAsync(request)
.thenApply(order -> ResponseEntity.ok(
new OrderResponse(order.getId(), "Created", order.getAmount())
))
.exceptionally(ex -> ResponseEntity.status(500).body(
new OrderResponse(-1, "Error: " + ex.getMessage(), 0)
));
}
// Multiple async calls combined
@GetMapping(value = "/{id}/full", responseType = FullOrderResponse.class)
public CompletableFuture<ResponseEntity<FullOrderResponse>> getFullOrder(
@PathVariable("id") String orderId) {
CompletableFuture<Order> orderFuture = orderService.getOrderAsync(orderId);
CompletableFuture<List<Payment>> paymentsFuture = paymentService.getPaymentsAsync(orderId);
CompletableFuture<List<Shipment>> shipmentsFuture = shipmentService.getShipmentsAsync(orderId);
// Combine all futures
return CompletableFuture.allOf(orderFuture, paymentsFuture, shipmentsFuture)
.thenApply(v -> {
FullOrderResponse response = new FullOrderResponse(
orderFuture.join(),
paymentsFuture.join(),
shipmentsFuture.join()
);
return ResponseEntity.ok(response);
});
}
}AsyncHandlerExecutor executor = AsyncHandlerExecutor.getInstance();
// Submit async task
CompletableFuture<Order> future = executor.submit(() -> {
// Blocking operation
return db.query("SELECT * FROM orders WHERE id = ?", id);
});
// Submit with timeout
CompletableFuture<Order> future = executor.submit(() -> {
return externalApi.call();
}, 5000); // 5 second timeout
// Get the executor for custom use
Executor ex = executor.getExecutor();// Service with dependency
@Service
public class OrderService {
@Autowired(required = false)
private NotificationService notificationService;
private final Map<String, Order> orders = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
System.out.println("[OrderService] Initialized");
}
public Order create(OrderRequest request) {
Order order = new Order(generateId(), request);
orders.put(order.id(), order);
if (notificationService != null) {
notificationService.notify("Order created: " + order.id());
}
return order;
}
@PreDestroy
public void cleanup() {
orders.clear();
System.out.println("[OrderService] Cleanup complete");
}
}// Interface
public interface PaymentService {
String processPayment(String orderId, double amount);
}
// Primary implementation (default)
@Service
@Primary
public class CreditCardPaymentService implements PaymentService {
@Override
public String processPayment(String orderId, double amount) {
return "CC-" + System.currentTimeMillis();
}
}
// Alternative implementations
@Service
public class PayPalPaymentService implements PaymentService {
@Override
public String processPayment(String orderId, double amount) {
return "PP-" + System.currentTimeMillis();
}
}
@Service
public class BankTransferPaymentService implements PaymentService {
@Override
public String processPayment(String orderId, double amount) {
return "BT-" + System.currentTimeMillis();
}
}
// Use in handler
@Component
public class PaymentHandler {
@Autowired
private PaymentService paymentService; // Gets @Primary (CreditCard)
@Autowired
@Qualifier("payPalPaymentService")
private PaymentService paypalService; // Gets PayPalPaymentService
@Autowired
@Qualifier("bankTransferPaymentService")
private PaymentService bankService; // Gets BankTransferPaymentService
}@Configuration
public class AppConfig {
@Bean
public ExecutorService taskExecutor() {
return Executors.newVirtualThreadPerTaskExecutor();
}
@Bean("appMetadata")
public AppMetadata appMetadata() {
return new AppMetadata("my-app", "3.0.0");
}
public record AppMetadata(String name, String version) {}
}@RequestMapping("/api/v1")
public class ApiController {
@GetMapping(value = "/products", responseType = ProductListResponse.class)
public ResponseEntity<ProductListResponse> getAllProducts() {
return ResponseEntity.ok(productService.getAll());
}
@GetMapping(value = "/products/{id}", responseType = ProductResponse.class)
public ResponseEntity<ProductResponse> getProductById(@PathVariable("id") String id) {
Product product = productService.find(id);
if (product == null) {
return ResponseEntity.notFound();
}
return ResponseEntity.ok(product);
}
@PostMapping(value = "/products", requestType = ProductRequest.class, responseType = ProductResponse.class)
@ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<ProductResponse> createProduct(@RequestBody @Valid ProductRequest request) {
return ResponseEntity.created(productService.save(request));
}
@PutMapping(value = "/products/{id}", requestType = ProductRequest.class, responseType = ProductResponse.class)
public ResponseEntity<ProductResponse> updateProduct(
@PathVariable("id") String id,
@RequestBody ProductRequest request) {
return ResponseEntity.ok(productService.update(id, request));
}
@DeleteMapping(value = "/products/{id}", responseType = Void.class)
@ResponseStatus(HttpStatus.NO_CONTENT)
public ResponseEntity<Void> deleteProduct(@PathVariable("id") String id) {
productService.delete(id);
return ResponseEntity.status(HttpStatus.NO_CONTENT);
}
}@PostMapping(value = "/orders", requestType = OrderRequest.class, responseType = OrderResponse.class)
public ResponseEntity<OrderResponse> createOrder(
@RequestBody @Valid OrderRequest request, // JSON body
@PathVariable("storeId") String storeId, // Path param: /stores/{storeId}/orders
@RequestParam("priority") String priority, // Query param: ?priority=high
@RequestParam(value = "notes", required = false) String notes, // Optional query param
@HeaderParam("X-Request-ID") String requestId, // Header
@HeaderParam("Authorization") String token, // Auth header
@CookieValue("sessionId") String sessionId // Cookie
) {
// All parameters are automatically resolved
return ResponseEntity.ok(orderService.create(request, storeId, priority));
}@GetMapping(value = "/products/search", responseType = ProductListResponse.class)
public ResponseEntity<ProductListResponse> searchProducts(
@RequestParam("q") String query, // Required
@RequestParam(value = "page", defaultValue = "1") int page, // Default value
@RequestParam(value = "size", defaultValue = "20") int size, // Default value
@RequestParam(value = "sort", required = false) String sort // Optional
) {
// GET /products/search?q=laptop&page=2&size=50&sort=price
return ResponseEntity.ok(productService.search(query, page, size, sort));
}Pre-computed annotation metadata at startup eliminates runtime reflection.
// Before: Runtime annotation lookup (~200ns)
Parameter param = method.getParameters()[i];
PathVariable pv = param.getAnnotation(PathVariable.class);
// After: Cached lookup (~5ns) - 40x faster
MethodMetadata metadata = MethodMetadata.getOrCreate(method, reqType, respType);
ParamInfo info = metadata.paramInfos[i];O(1) average lookup for HTTP parameters and headers.
// ThreadLocal pool - zero allocation
private static final ThreadLocal<FastMapV2> PARAM_MAP_POOL =
ThreadLocal.withInitial(FastMapV2::new);
// Usage
FastMapV2 params = PARAM_MAP_POOL.get();
params.clear();
parseParamsFast(params, queryString);
String value = params.get("key"); // O(1) averageDirect byte encoding eliminates String allocation.
// Before: String allocation
fn encode_headers(headers: &HeaderMap) -> String
// After: Zero-copy byte encoding
fn encode_headers_zero_copy(headers: &HeaderMap) -> Vec<u8>private static final byte[] ERROR_PREFIX = "{\"error\":\"".getBytes(UTF_8);
private static final byte[] ERROR_SUFFIX = "\"}".getBytes(UTF_8);
public static int writeErrorToBuffer(String message, ByteBuffer out, int offset) {
out.position(offset);
out.put(ERROR_PREFIX);
out.put(escapeJson(message).getBytes(UTF_8));
out.put(ERROR_SUFFIX);
return offset + ERROR_PREFIX.length + message.length() + ERROR_SUFFIX.length;
}docker build -t rust-java-rest:ultra -f src/main/resources/container/Dockerfile.ultra .docker run -d -p 8080:8080 --memory=50m --name rust-java rust-java-rest:ultra| Metric | Value |
|---|---|
| Memory Usage | 28.99 MB |
| CPU (idle) | ~6% |
| Image Size | 149 MB |
-Xms4m -Xmx24m
-XX:+UseSerialGC
-XX:MaxMetaspaceSize=20m
-XX:ReservedCodeCacheSize=8m
-XX:+TieredCompilation -XX:TieredStopAtLevel=1
-Xss256k@Component
@StaticFiles(path = "/static", location = "static")
public class StaticFileConfig {}This serves files from classpath:/static/ at /static/*:
GET /static/css/style.css → classpath:/static/css/style.css
GET /static/js/app.js → classpath:/static/js/app.js
GET /static/ → classpath:/static/index.html (default)
GET /static/images/logo.png → classpath:/static/images/logo.png
@Component
@StaticFiles(
path = "/public",
location = "public",
directoryListing = false, // Enable directory listing (default: false)
cacheMaxAge = 3600, // Cache max-age in seconds (default: 3600)
indexFile = "index.html" // Index file for directories (default: "index.html")
)
public class PublicStaticFiles {}@Component
@StaticFiles(path = "/assets", location = "assets")
public class AssetsConfig {}
@Component
@StaticFiles(path = "/uploads", location = "uploads", cacheMaxAge = 0)
public class UploadsConfig {}
@Component
@StaticFiles(path = "/", location = "public", indexFile = "index.html")
public class RootStaticFiles {}| Extension | MIME Type |
|---|---|
| .html, .htm | text/html |
| .css | text/css |
| .js | application/javascript |
| .json | application/json |
| .png | image/png |
| .jpg, .jpeg | image/jpeg |
| .gif | image/gif |
| .svg | image/svg+xml |
| .ico | image/x-icon |
| .webp | image/webp |
| .woff, .woff2 | font/woff, font/woff2 |
| .ttf | font/ttf |
| .mp4 | video/mp4 |
| .webm | video/webm |
| .mp3 | audio/mpeg |
| application/pdf | |
| .xml | application/xml |
| .txt | text/plain |
Small files (< 1MB) are automatically cached in memory for better performance.
// Clear cache programmatically (useful for development)
StaticFileRegistry.getInstance().clearCache();| Test Type | Where |
|---|---|
| Load/Benchmark/Stress | Docker Container |
| Functional/Unit | Local (mvn test) |
# Local - Functional Tests
mvn test
# Docker - Load Tests
docker run -d -p 8080:8080 --memory=50m rust-java-rest:ultra
wrk -t4 -c100 -d30s http://localhost:8080/health| File | Description |
|---|---|
bridge/MethodMetadata.java |
Pre-computed annotation metadata cache |
util/FastMapV2.java |
Robin-Hood hashing for O(1) lookup |
async/AsyncHandlerExecutor.java |
Virtual thread executor for async handlers |
websocket/WebSocketBroadcaster.java |
Room management and broadcasting |
websocket/WebSocketSession.java |
WebSocket session wrapper |
websocket/annotation/WebSocket.java |
@WebSocket annotation |
websocket/annotation/OnOpen.java |
@OnOpen lifecycle callback |
websocket/annotation/OnMessage.java |
@OnMessage handler |
websocket/annotation/OnClose.java |
@OnClose lifecycle callback |
websocket/annotation/OnError.java |
@OnError handler |
staticfiles/StaticFileRegistry.java |
Static file serving registry |
staticfiles/StaticFileScanner.java |
Scanner for @StaticFiles annotation |
annotations/StaticFiles.java |
@StaticFiles annotation |
container/Dockerfile.ultra |
Ultra-low memory Docker image |
CHANGELOG.md |
Detailed change history |
| Percentile | Latency |
|---|---|
| p50 | 5.5ms |
| p75 | 6.0ms |
| p90 | 6.7ms |
| p99 | 21.8ms |
| Avg | 5.8ms |
| Percentile | Latency |
|---|---|
| p50 | 6.6ms |
| p90 | 8.0ms |
| p99 | 28.0ms |
| Avg | 7.0ms |
No breaking changes. All v2.0.0 code works with v3.0.0.
Recommended:
- Update Maven dependency to 3.0.0
- Add WebSocket support if needed
- Use async handlers for I/O-bound operations
- Use
Dockerfile.ultrafor production
Full Changelog: https://github.com/esasmer-dou/rust-java-rest/compare/v2.0.0...v3.0.0