Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions buildSrc/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#
#Thu Nov 20 11:29:35 CET 2025
micronaut-build-version=8.0.0-M9
#Tue Dec 09 15:41:49 IST 2025
micronaut-build-version=8.0.0-M12
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ public interface TaskExecutors {
String IO = "io";

/**
* @deprecated Since 5.0.0. Use {@link #VIRTUAL} instead on JDK 21+, or migrate to dedicated executors suited for your workload.
* The name of the {@link java.util.concurrent.ExecutorService} used to schedule blocking tasks.
* If available, this will use {@link #VIRTUAL virtual threads}. Otherwise, it will fall back to
* {@link #IO}.
*/
@Deprecated(since = "5.0.0")
String BLOCKING = "blocking";

/**
* Recommended replacement for (@link #BLOCKING}) on JDK 21+.
* Executor that runs tasks on virtual threads. This requires JDK 19+, and
* {@code --enable-preview}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public class DefaultExecutorSelector implements ExecutorSelector {
protected DefaultExecutorSelector(
BeanLocator beanLocator,
@jakarta.inject.Named(TaskExecutors.IO) BeanProvider<ExecutorService> ioExecutor,
@jakarta.inject.Named(TaskExecutors.BLOCKING) BeanProvider<ExecutorService> blockingExecutor) {
@jakarta.inject.Named(TaskExecutors.VIRTUAL) BeanProvider<ExecutorService> blockingExecutor) {
this.beanLocator = beanLocator;
this.ioExecutor = SupplierUtil.memoized(ioExecutor::get);
this.blockingExecutor = SupplierUtil.memoized(blockingExecutor::get);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ ExecutorConfiguration virtual() {
* @return The blocking executor
*/
@Singleton
@Named(TaskExecutors.BLOCKING)
@Requires(missingProperty = ExecutorConfiguration.PREFIX + "." + TaskExecutors.BLOCKING)
@Named(TaskExecutors.VIRTUAL)
@Requires(missingProperty = ExecutorConfiguration.PREFIX + "." + TaskExecutors.VIRTUAL)
ExecutorService blocking(
@Named(TaskExecutors.IO) BeanProvider<ExecutorService> io,
@Named(TaskExecutors.VIRTUAL) BeanProvider<ExecutorService> virtual
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public enum ThreadSelection {
/**
* Automatically select the thread to run operations on based on the return type and/or {@link io.micronaut.core.annotation.Blocking} or {@link io.micronaut.core.annotation.NonBlocking} annotations.
*
* <p>This is the default strategy in 1.x and will run operations on the {@link io.micronaut.scheduling.TaskExecutors#BLOCKING blocking executor} if the return type
* <p>This is the default strategy in 1.x and will run operations on the {@link io.micronaut.scheduling.TaskExecutors#VIRTUAL blocking executor} if the return type
* of the method is not a reactive top and the method is not annotated with {@link io.micronaut.core.annotation.NonBlocking}</p>
*
* <p>If the return type is a reactive type and the method is not annotated with {@link io.micronaut.core.annotation.Blocking} then the server event loop thread will used to run the operation.</p>
Expand All @@ -41,7 +41,7 @@ public enum ThreadSelection {
*/
IO,
/**
* I/O selection will run all operations regardless of return type and annotations on the {@link io.micronaut.scheduling.TaskExecutors#BLOCKING blocking executor} and will never schedule an operation on the server event loop thread.
* I/O selection will run all operations regardless of return type and annotations on the {@link io.micronaut.scheduling.TaskExecutors#VIRTUAL blocking executor} and will never schedule an operation on the server event loop thread.
*/
BLOCKING
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class HttpProxySpec extends Specification {
}

@Get
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
String get(HttpRequest<?> request) {
"Proxied " + ctx.createBean(HttpClient, "http://localhost:${ctx.getBean(TargetPort).port}".toURI())
.toBlocking()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public void testContinueFormEncoded(boolean blocking) throws IOException {
@Requires(property = "spec.name", value = SPEC_NAME)
public static class SimpleController {
@Post("/plain")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
public String plain(@Body Publisher<String> data) {
return String.join("", Flux.from(data).collectList().block());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public void echoLongInputStream() throws Exception {
InputStreamByteBody.create(
new ByteArrayInputStream(LONG_PAYLOAD),
OptionalLong.of(LONG_PAYLOAD.length),
server.getApplicationContext().getBean(Executor.class, Qualifiers.byName(TaskExecutors.BLOCKING)),
server.getApplicationContext().getBean(Executor.class, Qualifiers.byName(TaskExecutors.VIRTUAL)),
ByteBodyFactory.createDefault(ByteArrayBufferFactory.INSTANCE)
),
null
Expand Down Expand Up @@ -240,7 +240,7 @@ public HttpResponse<?> filterRequestReplaceResponse(HttpRequest<?> request, @Bod
}

@ResponseFilter("/filter-replace-response")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
public HttpResponse<?> filterReplaceResponse(HttpResponse<?> response) throws Exception {
try (ByteBodyHttpResponse<?> r = (ByteBodyHttpResponse<?>) response) {
return HttpResponse.ok("Replaced response. Response body: " + r.byteBody().buffer().get().toString(StandardCharsets.UTF_8));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ public CompletionStage<HttpRequest<Object>> requestFilterReplaceRequestCompletio
}

@RequestFilter("/request-filter/continuation-blocking")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
public void requestFilterContinuationBlocking(MutableHttpRequest<?> request, FilterContinuation<HttpResponse<?>> continuation) {
request.header("foo", "bar");
HttpResponse<?> r = continuation.proceed();
Expand All @@ -432,7 +432,7 @@ public Publisher<HttpResponse<?>> requestFilterContinuationReactivePublisher(Mut
}

@RequestFilter("/request-filter/continuation-update-request")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
public void requestFilterContinuationUpdateRequest(FilterContinuation<HttpResponse<?>> continuation) {
// won't affect the routing decision, but will appear in the controller
continuation.request(HttpRequest.GET("/request-filter/continuation-update-request-2"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ public <I, O, E> HttpResponse<O> exchange(io.micronaut.http.HttpRequest<I> reque
You are trying to run a BlockingHttpClient operation on a netty event \
loop thread. This is a common cause for bugs: Event loops should \
never be blocked. You can either mark your controller as \
@ExecuteOn(TaskExecutors.BLOCKING), or use the reactive HTTP client \
@ExecuteOn(TaskExecutors.VIRTUAL), or use the reactive HTTP client \
to resolve this bug. There is also a configuration option to \
disable this check if you are certain a blocking operation is fine \
here.""");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ public DefaultNettyHttpClientRegistry(
EventLoopGroupFactory eventLoopGroupFactory,
BeanContext beanContext,
JsonMapper jsonMapper,
@Nullable @Named(TaskExecutors.BLOCKING) ExecutorService blockingExecutor) {
@Nullable @Named(TaskExecutors.VIRTUAL) ExecutorService blockingExecutor) {
this.clientFilterResolver = httpClientFilterResolver;
this.defaultHttpClientConfiguration = defaultHttpClientConfiguration;
this.loadBalancerResolver = loadBalancerResolver;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class NonMutableResponseSpec extends Specification {
ResponseClient responseClient

@Get('/test/non-mutable')
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
HttpResponse<String> go() {
responseClient.go()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ class ReadTimeoutSpec extends Specification {
}

@Get(value = "/client", produces = MediaType.TEXT_PLAIN)
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
String test() {
return testClient.get()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class StickyEventLoopSpec extends Specification {
}

@Get("/concurrent")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
def concurrent() {
latch.countDown()
latch.await()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public NettyHttpServer(
.getEventPublisher(HttpRequestReceivedEvent.class);
final Supplier<ExecutorService> ioExecutor = SupplierUtil.memoized(() ->
nettyEmbeddedServices.getExecutorSelector()
.select(TaskExecutors.BLOCKING).orElse(null)
.select(TaskExecutors.VIRTUAL).orElse(null)
);
this.routingHandler = new RoutingInBoundHandler(
serverConfiguration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public final class NettyServerRequestBinderRegistry implements RequestBinderRegi
public NettyServerRequestBinderRegistry(ConversionService conversionService,
List<RequestArgumentBinder> binders,
BeanProvider<NettyHttpServerConfiguration> httpServerConfiguration,
@Named(TaskExecutors.BLOCKING)
@Named(TaskExecutors.VIRTUAL)
BeanProvider<ExecutorService> executorService,
MessageBodyHandlerRegistry bodyHandlerRegistry) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class FormLimitSpec extends Specification {
@Requires(property = "spec.name", value = "FormLimitSpec")
static class MyCtrl {
@Post
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
@Consumes([MediaType.APPLICATION_FORM_URLENCODED, MediaType.MULTIPART_FORM_DATA])
String post(@Body MultipartBody body) {
return "attributes: " + Flux.from(body).doOnNext { it.getBytes() }.count().block()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class LoomCarrierSpec extends Specification {
@Inject
JsonMapper jsonMapper

@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
@Get
ThreadInfo threadInfo() {
return new ThreadInfo(
Expand All @@ -116,7 +116,7 @@ class LoomCarrierSpec extends Specification {
)
}

@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
@Get("/loop-jdk")
String loopJdk() {
def scheduler = EventLoopVirtualThreadScheduler.current()
Expand All @@ -138,13 +138,13 @@ class LoomCarrierSpec extends Specification {
}
}

@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
@Get("/loop-mn")
String loopMn() {
return client.toBlocking().retrieve("/loom-carrier")
}

@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
@Get("/loop-read")
LoopRead loopRead() {
def before = threadInfo()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,15 @@ class HttpClientFilterRequestInReactorContextSpec extends Specification {
@Get("/foo")
@Produces(MediaType.TEXT_PLAIN)
@SingleResult
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
String foo() {
fooClient.hello()
}

@Get("/bar")
@Produces(MediaType.TEXT_PLAIN)
@SingleResult
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
String bar() {
fooClient.hello()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

@Requires(property = "spec.name", value = "PropagatedContextExceptionHandlerTest")
@Controller("/blocking")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
public class BlockingController {

@Get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class FilterFormSpec extends Specification {
@ServerFilter("/form-error")
static class Fltr {
@RequestFilter
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
void filterRequest() {
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class FiltersPropagatedContextSpec6 extends Specification {
static class Filter1 implements Ordered {

@RequestFilter
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
MutableHttpResponse<?> myFilter(HttpRequest<?> request, FilterContinuation<MutableHttpResponse<?>> continuation) {
try (PropagatedContext.Scope ignore = PropagatedContext.getOrEmpty().plus(new MyContext()).propagate()) {
return continuation.request(request).proceed()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ class FiltersPropagatedContextSpec7 extends Specification {
static class Filter1 {

@RequestFilter
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
MutableHttpResponse<?> myFilter(HttpRequest<?> request, FilterContinuation<MutableHttpResponse<?>> continuation) {
try (PropagatedContext.Scope ignore = PropagatedContext.getOrEmpty().plus(new MyContext()).propagate()) {
return continuation.request(request).proceed()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class FiltersPropagatedContextSpec8 extends Specification {
static class Filter1 implements Ordered {

@RequestFilter
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
MutableHttpResponse<?> myFilter(HttpRequest<?> request,
FilterContinuation<MutableHttpResponse<?>> continuation,
MutablePropagatedContext mutablePropagatedContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class ServerFilterSpec extends Specification {
@Singleton
@Requires(property = "spec.name", value = "ServerFilterSpec")
@ServerFilter("/blocking-filter/**")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
static class BlockingFilter {
def events = new ArrayList<String>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ class ServerPreFilterSpec extends Specification {
@Singleton
@Requires(property = "spec.name", value = "ServerPreFilterSpec")
@ServerFilter("/blocking-filter/**")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
static class BlockingFilter {
def events = new ArrayList<String>()

Expand Down Expand Up @@ -329,7 +329,7 @@ class ServerPreFilterSpec extends Specification {
@Singleton
@Requires(property = "spec.name", value = "ServerPreFilterSpec")
@ServerFilter("/blocking-pre-matching-filter/**")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
static class BlockingPreMatchingFilter {
def events = new ArrayList<String>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

@Requires(property = "spec.name", value = "RootRoutingTest")
@Controller
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
public class MyController {

@Post
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ class RequestScopeSpec extends AbstractMicronautSpec {
}

@Get("/test-simple-request-scope")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
String test() {
return simpleRequestBean.sayHello()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class WebsocketExecuteOnSpec extends Specification {

@Requires(property = "spec.name", value = "WebsocketExecuteOnSpec")
@ServerWebSocket("/echo/sync")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
static class SynchronousEchoServerWebSocket {

@Inject
Expand Down Expand Up @@ -167,7 +167,7 @@ class WebsocketExecuteOnSpec extends Specification {

@Requires(property = "spec.name", value = "WebsocketExecuteOnSpec")
@ServerWebSocket("/echo/reactive")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
static class ReactiveEchoServerWebSocket {

@Inject
Expand Down Expand Up @@ -211,7 +211,7 @@ class WebsocketExecuteOnSpec extends Specification {

@Requires(property = "spec.name", value = "WebsocketExecuteOnSpec")
@ServerWebSocket("/echo/async")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
static class AsyncEchoServerWebSocket {

@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ public void requestFilterImmediateRequestParameter(HttpRequest<?> request) {
}

@RequestFilter("/request-filter/binding")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
public void requestFilterBinding(
@Header String contentType,
@Body byte[] bytes,
Expand Down Expand Up @@ -501,7 +501,7 @@ public CompletionStage<HttpRequest<Object>> requestFilterReplaceRequestCompletio
}

@RequestFilter("/request-filter/continuation-blocking")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
public void requestFilterContinuationBlocking(HttpRequest<?> request, FilterContinuation<HttpResponse<?>> continuation) {
request.setAttribute("foo", "bar");
HttpResponse<?> r = continuation.proceed();
Expand All @@ -515,7 +515,7 @@ public Publisher<HttpResponse<?>> requestFilterContinuationReactivePublisher(Htt
}

@RequestFilter("/request-filter/continuation-update-request")
@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
public void requestFilterContinuationUpdateRequest(FilterContinuation<HttpResponse<?>> continuation) {
// won't affect the routing decision, but will appear in the controller
continuation.request(HttpRequest.GET("/request-filter/continuation-update-request-2"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ static class CsrfFilter {
this.bodyParser = bodyParser;
}

@ExecuteOn(TaskExecutors.BLOCKING)
@ExecuteOn(TaskExecutors.VIRTUAL)
@RequestFilter
@Nullable
public HttpResponse<?> csrfFilter(@NonNull HttpRequest<?> request) {
Expand Down
Loading
Loading