Skip to content

Latest commit

 

History

History
1097 lines (837 loc) · 36.2 KB

File metadata and controls

1097 lines (837 loc) · 36.2 KB

Rust-Java REST Framework v3.3.1

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.

What's New For Users

  • 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.

Maven Dependency

<dependency>
    <groupId>com.reactor</groupId>
    <artifactId>rust-java-rest</artifactId>
    <version>3.3.1</version>
</dependency>

Full release notes:


Rust-Java REST Framework v3.2.5

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.

What's New For Users

  • Maven dependency version is now 3.2.5.
  • Native resources are refreshed for java-rust-dubbo:0.2.0 native response handle usage.
  • Redis native ABI version 3 remains available for java-rust-cache:0.2.1 Cluster and Sentinel scenarios.
  • Existing REST annotations and Java handler/service code remain source-compatible.
  • Production artifact rules from the 3.2.x line still apply: normal dependency for applications, sample classifier only for demos/benchmarks.

Maven Dependency

<dependency>
    <groupId>com.reactor</groupId>
    <artifactId>rust-java-rest</artifactId>
    <version>3.2.5</version>
</dependency>

Current Compatibility Rule

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:


Rust-Java REST Framework v3.2.2

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.

What's New For Users

  • 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.
  • JsonBodyProducer can be returned directly for default 200 OK JSON 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.

Maven Dependency

<dependency>
    <groupId>com.reactor</groupId>
    <artifactId>rust-java-rest</artifactId>
    <version>3.2.2</version>
</dependency>

Current Gate Signal

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:


Rust-Java REST Framework v3.2.1

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.

What's New For Users

  • Maven dependency version is now 3.2.1.
  • Production-like benchmark images can use rust-java-rest-*-core-runtime.jar instead of framework target/classes.
  • Default jar, core-runtime, sources jar, and javadocs exclude framework sample/benchmark packages.
  • sample classifier 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.

Maven Dependency

<dependency>
    <groupId>com.reactor</groupId>
    <artifactId>rust-java-rest</artifactId>
    <version>3.2.1</version>
</dependency>

Production Artifact Rule

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 6

Use the bundled sample app only when you are intentionally testing bundled demo endpoints.

Optional JIT-Cap Evidence

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.

Validation

  • mvn -q test
  • mvn -q -DskipTests package
  • Jar policy check for main jar, core-runtime, and sources jar.
  • Native-static Dubbo consumer smoke benchmark with -FrameworkArtifactMode core-runtime.

Rust-Java REST Framework v3.1.0

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.

What's New for Users

  • Immutable FileResponse routes 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 -FrameworkJavaOptsAppend for 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-rc4 remain in place for response bodies, path variables, request params, cookies, middleware query helpers, and WebSocket params.

Which API Should I Use?

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 Guide

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

Maven Dependency

<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.dll
  • native/linux-x64/librust_hyper.so
  • legacy Windows resource path rust_hyper.dll

GitHub Release assets expose platform-explicit names:

  • rust_hyper-windows-x64.dll
  • librust_hyper-linux-x64.so

Validation

  • mvn -q test
  • mvn -q -DskipTests package
  • cargo test
  • Windows native DLL rebuild
  • Linux native SO rebuild
  • General benchmark: current_full_20260531_090441, profile low-rss, CPU 2, Rust-Java memory 96m, Spring Boot memory 512m, concurrency 64/256/512/1000, repeat 1.
  • Large file stream matrix: stream_matrix_{32,64,128,256}_20260531_085157, 8 MiB file, inline disabled, concurrency 256/512/1000.

Current Low-RSS Benchmark Snapshot

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

Large File Stream Gate

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.


Rust-Java REST Framework v3.0.1

Note: v3.0.1 fixes native library updates. See v3.0.0 below for full feature list.


Rust-Java REST Framework v3.0.0

Major Release: Ultra Low Latency & Complete Feature Set

This release combines extreme performance optimization with a complete REST API feature set including WebSocket support, async handlers, and dependency injection.


Maven Dependency

<dependency>
    <groupId>com.reactor</groupId>
    <artifactId>rust-java-rest</artifactId>
    <version>3.0.0</version>
</dependency>

GitHub Packages Repository

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>

Gradle

repositories {
    maven {
        url = uri("https://maven.pkg.github.com/esasmer-dou/rust-java-rest")
    }
}

dependencies {
    implementation 'com.reactor:rust-java-rest:3.0.0'
}

Performance Improvements

Latency Reduction (Phase 5 Optimizations)

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

Memory Footprint

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 Overview

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

1. WebSocket Support

Echo Server Example

@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);
    }
}

Chat Room with Path Parameters

@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);
        }
    }
}

WebSocket Broadcasting API

// 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();

WebSocket Client (JavaScript)

// 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);

2. Async Handlers (CompletableFuture)

Async Handler with Virtual Threads

@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;
        });
    }
}

Handler Using Async Service

@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 API

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();

3. Dependency Injection (Zero-Overhead)

Define Services

// 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");
    }
}

Multiple Implementations with @Primary and @Qualifier

// 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 with @Bean

@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) {}
}

4. REST API Annotations

HTTP Method Mappings

@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);
    }
}

Parameter Annotations

@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));
}

Query Parameters with Defaults

@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));
}

5. Phase 5 Optimizations

MethodMetadata Cache

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];

FastMapV2 with Robin-Hood Hashing

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) average

Zero-Copy Header Encoding (Rust)

Direct 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>

Pre-Allocated Error Responses

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;
}

6. Docker - Ultra Low Memory

Build

docker build -t rust-java-rest:ultra -f src/main/resources/container/Dockerfile.ultra .

Run with 50MB Memory Limit

docker run -d -p 8080:8080 --memory=50m --name rust-java rust-java-rest:ultra

Container Stats

Metric Value
Memory Usage 28.99 MB
CPU (idle) ~6%
Image Size 149 MB

JVM Options

-Xms4m -Xmx24m
-XX:+UseSerialGC
-XX:MaxMetaspaceSize=20m
-XX:ReservedCodeCacheSize=8m
-XX:+TieredCompilation -XX:TieredStopAtLevel=1
-Xss256k

7. Static File Serving

Basic Configuration

@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

Full Configuration Options

@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 {}

Multiple Static File Locations

@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 {}

Supported MIME Types

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
.pdf application/pdf
.xml application/xml
.txt text/plain

File Caching

Small files (< 1MB) are automatically cached in memory for better performance.

// Clear cache programmatically (useful for development)
StaticFileRegistry.getInstance().clearCache();

8. Test Strategy (Rule #18)

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

New Files

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

Benchmark Results

GET /health (100 sequential requests)

Percentile Latency
p50 5.5ms
p75 6.0ms
p90 6.7ms
p99 21.8ms
Avg 5.8ms

POST /order/create (50 requests)

Percentile Latency
p50 6.6ms
p90 8.0ms
p99 28.0ms
Avg 7.0ms

Migration from v2.0.0

No breaking changes. All v2.0.0 code works with v3.0.0.

Recommended:

  1. Update Maven dependency to 3.0.0
  2. Add WebSocket support if needed
  3. Use async handlers for I/O-bound operations
  4. Use Dockerfile.ultra for production

Full Changelog: https://github.com/esasmer-dou/rust-java-rest/compare/v2.0.0...v3.0.0