Execution Modes & Return Types
The extension supports several return types, each with different execution semantics. You can further control threading with the @Blocking and @NonBlocking annotations.
Synchronous Methods
Plain return types run on a worker thread by default (blocking):
@JsonRPCApi
public class MyService {
// Runs on executor-thread (blocking, default)
public String compute(String input) {
return expensiveOperation(input);
}
// Runs on vert.x-eventloop-thread (non-blocking)
@NonBlocking
public String computeFast(String input) {
return cheapOperation(input);
}
}
| Return type | Default execution |
|---|---|
Plain type (String, int, POJO, …) |
Blocking — worker thread via |
Plain type + |
Non-blocking — Vert.x event loop |
Uni (Async Single Result)
Uni<T> from Mutiny returns a single async result:
@JsonRPCApi
public class MyService {
// Non-blocking by default
public Uni<String> fetchData(String id) {
return dataClient.get(id);
}
// Force blocking execution
@Blocking
public Uni<String> fetchDataBlocking(String id) {
return Uni.createFrom().item(blockingLookup(id));
}
}
| Return type | Default execution |
|---|---|
|
Non-blocking — event loop |
|
Blocking — wrapped in |
CompletionStage / CompletableFuture
Standard Java async types are also supported and behave like Uni<T>:
@JsonRPCApi
public class MyService {
public CompletionStage<String> fetchAsync(String id) {
return CompletableFuture.supplyAsync(() -> lookup(id));
}
}
Multi (Streaming)
Multi<T> enables server-sent streaming via JSON-RPC subscriptions. See Streaming with Multi for the full protocol details.
@JsonRPCApi
public class MyService {
public Multi<String> ticker() {
return Multi.createFrom().ticks().every(Duration.ofSeconds(1))
.onItem().transform(n -> "tick " + n);
}
}
Flow.Publisher<T> is also supported and treated identically to Multi<T>.
Virtual Threads
If your application runs on Java 21+, you can use @RunOnVirtualThread to execute methods on virtual threads instead of the platform worker thread pool. This gives you the simple blocking programming model with much better scalability — virtual threads are cheap and plentiful, so you won’t exhaust a fixed thread pool under load.
@JsonRPCApi
public class MyService {
// Runs on a virtual thread
@RunOnVirtualThread
public String fetchData(String id) {
return blockingHttpClient.get(id); // blocking I/O is fine here
}
// Also works with Uni
@RunOnVirtualThread
public Uni<String> fetchDataUni(String id) {
return Uni.createFrom().item(blockingHttpClient.get(id));
}
}
To use @RunOnVirtualThread, add the quarkus-virtual-threads extension to your project:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-virtual-threads</artifactId>
</dependency>
| Combination | Behavior |
|---|---|
|
Executes on a virtual thread |
|
Executes on a virtual thread ( |
|
Build error — these are contradictory |
Summary
| Return type | Default | Override |
|---|---|---|
Plain type |
Blocking |
|
|
Non-blocking |
|
|
Non-blocking |
|
|
Non-blocking |
— |
|
Non-blocking |
— |
A method cannot be annotated with both @Blocking and @NonBlocking, or both @RunOnVirtualThread and @NonBlocking. The build will fail if conflicting annotations are present.
|