Repository: jooby-project/jooby
Pull request #3470 (2024-07-10 16:09:27)

Bump Avaje Inject to 10.0
as the title says


Patch:
diff --git a/README.md b/README.md
index 7255df9ec..0d83de234 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
 [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.jooby/jooby/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.jooby/jooby)
 [![Javadoc](https://javadoc.io/badge/io.jooby/jooby.svg)](https://javadoc.io/doc/io.jooby/jooby/latest)
 [![Github](https://github.com/jooby-project/jooby/workflows/Full%20Build/badge.svg)](https://github.com/jooby-project/jooby/actions)
+[![Slack](https://img.shields.io/badge/slack-chat-brightgreen.svg?logo=slack)](https://joobyio.slack.com)
 [![Discord](https://img.shields.io/discord/1225457509909922015?label=discord)](https://discord.gg/nmfJmmrq)
 [![Become a Patreon](https://img.shields.io/badge/patreon-donate-orange.svg)](https://patreon.com/edgarespina)
 [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg?logo=paypal&style=flat-square)](https://paypal.me/edgarespina)
@@ -49,7 +50,7 @@ Documentation is available at [https://jooby.io](https://jooby.io)
 help
 =====
 
-[Discord](https://discord.gg/nmfJmmrq)
+[Slack](https://joobyio.slack.com)
 
 donate & support
 =====
@@ -59,11 +60,12 @@ donate & support
 - [support@jooby.io](mailto:support@jooby.io?Subject=Jooby%20Support)
 
 
-Previous version
+1.x version
 =====
 
-- v2: [Documentation](https://jooby.io/v2) and [source code](https://github.com/jooby-project/jooby/tree/2.x)
-- v1: [Documentation](https://jooby.io/v1) and [source code](https://github.com/jooby-project/jooby/tree/1.x)
+Documentation for 1.x is available at [https://jooby.io/v1](https://jooby.io/v1)
+
+Source code for 1.x is available at the [1.x branch](https://github.com/jooby-project/jooby/tree/1.x)
 
 author
 =====
diff --git a/docs/asciidoc/modules/avaje-inject.adoc b/docs/asciidoc/modules/avaje-inject.adoc
index 490f68541..eeb991374 100644
--- a/docs/asciidoc/modules/avaje-inject.adoc
+++ b/docs/asciidoc/modules/avaje-inject.adoc
@@ -5,8 +5,44 @@
 [dependency, groupId="io.jooby", artifactId="jooby-avaje-inject", version="1.1.0"]
 .
 
+2) Configure annotation processor
 
-2) Install Avaje Inject:
+.Maven
+[source, xml, role = "primary"]
+----
+<build>
+  <plugins>
+    <plugin>
+      <groupId>org.apache.maven.plugins</groupId>
+      <artifactId>maven-compiler-plugin</artifactId>
+      <version>...</version>
+      <configuration>
+        <annotationProcessorPaths>
+          <path>
+            <groupId>io.avaje</groupId>
+            <artifactId>avaje-inject-generator</artifactId>
+            <version>10.0</version>
+          </path>
+        </annotationProcessorPaths>
+      </configuration>
+    </plugin>
+  </plugins>
+</build>
+----
+
+.Gradle
+[source, kotlin, role = "secondary"]
+----
+plugins {
+  id "org.jetbrains.kotlin.kapt" version "1.9.10"
+}
+
+dependencies {
+  kapt 'io.avaje:avaje-inject-generator:10.0'
+}
+----
+
+3) Install Avaje Inject:
 
 .Installing Avaje Inject
 [source,java,role = "primary"]
@@ -48,7 +84,7 @@ fun main(args: Array<String>) {
 
 ==== Property Injection
 
-Configuration properties can be injected using the `@Named` annotation. As Avaje checks beans at compile time, `@InjectModule(requires={String.class})`:
+Configuration properties can be injected using the `@Named` annotation. As Avaje checks beans at compile time, `@External` is required to prevent false-positive compilation errors:
 
 .application.conf
 [source, bash]
@@ -63,7 +99,7 @@ currency = USD
 public class BillingService {
 
   @Inject
-  public BillingService(@Named("currency") String currency) {
+  public BillingService(@External @Named("currency") String currency) {
     ...
   }
 
@@ -74,7 +110,7 @@ public class BillingService {
 [source,kotlin,role="secondary"]
 ----
 @Singleton
-class BillingService @Inject constructor(@Named("currency") currency: String) {
+class BillingService @Inject constructor(@External @Named("currency") currency: String) {
   ...
 }
 ----
@@ -117,5 +153,5 @@ fun main(args: Array<String>) {
 
 The lifecycle of `MyController` is now managed by Avaje Inject.
 
-In Avaje Inject, the dependency graph is typically validated when the application compiles. As beans provided by Jooby Modules are registered at runtime, you must add `@InjectModules(requires={String.class, <other Jooby Module Provided Bean ClassNames>...})` to inform the avaje processor that these beans are provided at runtime.
+In Avaje Inject, the dependency graph is typically validated when the application compiles. As beans provided by Jooby Modules are registered at runtime, you must add `@External` when injecting these runtime beans into `@Singleton` classes to inform the avaje processor that these beans are provided at runtime.
 
diff --git a/jooby/pom.xml b/jooby/pom.xml
index e398a4497..dc3656a20 100644
--- a/jooby/pom.xml
+++ b/jooby/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>jooby-project</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/jooby/src/main/java/io/jooby/Jooby.java b/jooby/src/main/java/io/jooby/Jooby.java
index 12d3d0d98..40df26a23 100644
--- a/jooby/src/main/java/io/jooby/Jooby.java
+++ b/jooby/src/main/java/io/jooby/Jooby.java
@@ -552,7 +552,7 @@ public class Jooby implements Router, Registry {
   public <T> Jooby mvc(@NonNull Class<T> router, @NonNull Provider<T> provider) {
     try {
       MvcFactory module = loadModule(router);
-      Extension extension = module.create(provider::get);
+      Extension extension = module.create(provider);
       extension.install(this);
       return this;
     } catch (Exception x) {
@@ -560,7 +560,7 @@ public class Jooby implements Router, Registry {
     }
   }
 
-  private <T> MvcFactory<T> loadModule(Class<T> router) {
+  private MvcFactory loadModule(Class router) {
     try {
       ServiceLoader<MvcFactory> modules = ServiceLoader.load(MvcFactory.class);
       return stream(modules.spliterator(), false)
@@ -1079,7 +1079,7 @@ public class Jooby implements Router, Registry {
 
     this.startingCallbacks = fire(this.startingCallbacks);
 
-    router.start(this, server);
+    router.start(this);
 
     return this;
   }
diff --git a/jooby/src/main/java/io/jooby/MvcFactory.java b/jooby/src/main/java/io/jooby/MvcFactory.java
index 2771d700d..d6f72ddcf 100644
--- a/jooby/src/main/java/io/jooby/MvcFactory.java
+++ b/jooby/src/main/java/io/jooby/MvcFactory.java
@@ -6,6 +6,7 @@
 package io.jooby;
 
 import edu.umd.cs.findbugs.annotations.NonNull;
+import jakarta.inject.Provider;
 
 /**
  * Created by a Jooby annotation processor tool using the {@link java.util.ServiceLoader} API.
@@ -27,5 +28,5 @@ public interface MvcFactory<T> {
    * @param provider MVC route instance provider.
    * @return All mvc route as extension module.
    */
-  @NonNull Extension create(@NonNull java.util.function.Supplier<T> provider);
+  @NonNull Extension create(@NonNull Provider<T> provider);
 }
diff --git a/jooby/src/main/java/io/jooby/annotation/package-info.java b/jooby/src/main/java/io/jooby/annotation/package-info.java
deleted file mode 100644
index 971e018be..000000000
--- a/jooby/src/main/java/io/jooby/annotation/package-info.java
+++ /dev/null
@@ -1,3 +0,0 @@
-/** Supported annotations for creating MVC routes. */
-@edu.umd.cs.findbugs.annotations.ReturnValuesAreNonnullByDefault
-package io.jooby.annotation;
diff --git a/jooby/src/main/java/io/jooby/buffer/README b/jooby/src/main/java/io/jooby/buffer/README
deleted file mode 100644
index b44dca9b5..000000000
--- a/jooby/src/main/java/io/jooby/buffer/README
+++ /dev/null
@@ -1,10 +0,0 @@
-Generic abstraction for working with byte buffer implementations.
-
-Copy from: https://github.com/spring-projects/spring-framework/tree/main/spring-core/src/main/java/org/springframework/core/io/buffer.
-
-- Copy all package inside io.jooby.byffer
-- remove all Assert/ObjectUtils import references
-- remove Netty5DataBuffer references
-- replace reactive stream classes references by JDK reactive streams
-- DataBufferUtils is a limited version of original - remove Deprecated methods
-- Remove all deprecated since 6.0 from DataBuffer and DataBufferFactory
diff --git a/jooby/src/main/java/io/jooby/buffer/package-info.java b/jooby/src/main/java/io/jooby/buffer/package-info.java
index feb1b7e08..a21b93d58 100644
--- a/jooby/src/main/java/io/jooby/buffer/package-info.java
+++ b/jooby/src/main/java/io/jooby/buffer/package-info.java
@@ -1,20 +1,3 @@
-/*
- * This file is part of Jooby.
- *
- * It is derived from Spring Framework, originally available at: https://github.com/spring-projects/spring-framework
- *
- * Spring Framework is licensed under the Apache License, Version 2.0.
- *
- * Modifications:
- * - Code live inside of io.jooby.buffer package
- * - Added DataBuffer.duplicate
- * - Added DataBufferFactory.wrap(byte[] bytes, int offset, int length)
- *
- * Jooby is also licensed under the Apache License, Version 2.0.
- *
- * See the LICENSE file in the root of this repository for details.
- */
-
 /**
  * Generic abstraction for working with byte buffer implementations.
  *
@@ -30,5 +13,4 @@
  *   <li>Remove all deprecated since 6.0 from DataBuffer and DataBufferFactory
  * </ul>
  */
-@edu.umd.cs.findbugs.annotations.ReturnValuesAreNonnullByDefault
 package io.jooby.buffer;
diff --git a/jooby/src/main/java/io/jooby/exception/package-info.java b/jooby/src/main/java/io/jooby/exception/package-info.java
deleted file mode 100644
index eafcb7be3..000000000
--- a/jooby/src/main/java/io/jooby/exception/package-info.java
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Built-in exceptions for common HTTP error codes. */
-package io.jooby.exception;
diff --git a/jooby/src/main/java/io/jooby/handler/package-info.java b/jooby/src/main/java/io/jooby/handler/package-info.java
deleted file mode 100644
index c3d441500..000000000
--- a/jooby/src/main/java/io/jooby/handler/package-info.java
+++ /dev/null
@@ -1,3 +0,0 @@
-/** Built-in middleware. */
-@edu.umd.cs.findbugs.annotations.ReturnValuesAreNonnullByDefault
-package io.jooby.handler;
diff --git a/jooby/src/main/java/io/jooby/internal/Chi.java b/jooby/src/main/java/io/jooby/internal/Chi.java
index 14e16e4bd..6234518e8 100644
--- a/jooby/src/main/java/io/jooby/internal/Chi.java
+++ b/jooby/src/main/java/io/jooby/internal/Chi.java
@@ -103,414 +103,6 @@ class Chi implements RouteTree {
     }
   }
 
-  private interface StaticMap {
-    StaticMap INIT =
-        new StaticMap() {
-          @Override
-          public StaticRoute get(String path) {
-            return null;
-          }
-
-          @Override
-          public StaticMap put(String path, StaticRoute staticRoute) {
-            return new StaticMap1(path, staticRoute);
-          }
-        };
-
-    StaticRoute get(String path);
-
-    StaticMap put(String path, StaticRoute staticRoute);
-  }
-
-  private static class StaticMap1 implements StaticMap {
-    private String pattern;
-    private StaticRoute route;
-
-    public StaticMap1(String pattern, StaticRoute route) {
-      this.pattern = pattern;
-      this.route = route;
-    }
-
-    @Override
-    public StaticMap put(String path, StaticRoute route) {
-      if (pattern.equals(path)) {
-        // Override
-        this.route = route;
-        return this;
-      }
-      return new StaticMap2(this, path, route);
-    }
-
-    public void release() {
-      this.pattern = null;
-      this.route = null;
-    }
-
-    public StaticRoute get(String path) {
-      if (pattern.equals(path)) {
-        return route;
-      } else {
-        return null;
-      }
-    }
-  }
-
-  private static class StaticMap2 implements StaticMap {
-    private String pattern1;
-    private StaticRoute route1;
-    private String pattern2;
-    private StaticRoute route2;
-
-    public StaticMap2(StaticMap1 staticMap, String path, StaticRoute route) {
-      this.pattern1 = staticMap.pattern;
-      this.route1 = staticMap.route;
-      this.pattern2 = path;
-      this.route2 = route;
-      staticMap.release();
-    }
-
-    public void release() {
-      this.pattern1 = null;
-      this.route1 = null;
-      this.pattern2 = null;
-      this.route2 = null;
-    }
-
-    @Override
-    public StaticMap put(String path, StaticRoute route) {
-      if (pattern1.equals(path)) {
-        route1 = route;
-        return this;
-      } else if (pattern2.equals(path)) {
-        route2 = route;
-        return this;
-      }
-      return new StaticMap3(this, path, route);
-    }
-
-    public StaticRoute get(String path) {
-      if (pattern1.equals(path)) {
-        return route1;
-      } else if (pattern2.equals(path)) {
-        return route2;
-      } else {
-        return null;
-      }
-    }
-  }
-
-  private static class StaticMap3 implements StaticMap {
-    private String pattern1;
-    private StaticRoute route1;
-    private String pattern2;
-    private StaticRoute route2;
-    private String pattern3;
-    private StaticRoute route3;
-
-    public StaticMap3(StaticMap2 staticMap, String path, StaticRoute route) {
-      this.pattern1 = staticMap.pattern1;
-      this.route1 = staticMap.route1;
-      this.pattern2 = staticMap.pattern2;
-      this.route2 = staticMap.route2;
-      this.pattern3 = path;
-      this.route3 = route;
-      staticMap.release();
-    }
-
-    public void release() {
-      this.pattern1 = null;
-      this.route1 = null;
-      this.pattern2 = null;
-      this.route2 = null;
-      this.pattern3 = null;
-      this.route3 = null;
-    }
-
-    @Override
-    public StaticMap put(String path, StaticRoute route) {
-      if (pattern1.equals(path)) {
-        route1 = route;
-        return this;
-      } else if (pattern2.equals(path)) {
-        route2 = route;
-        return this;
-      } else if (pattern3.equals(path)) {
-        route3 = route;
-        return this;
-      }
-      return new StaticMap4(this, path, route);
-    }
-
-    public StaticRoute get(String path) {
-      if (pattern1.equals(path)) {
-        return route1;
-      } else if (pattern2.equals(path)) {
-        return route2;
-      } else if (pattern3.equals(path)) {
-        return route3;
-      } else {
-        return null;
-      }
-    }
-  }
-
-  private static class StaticMap4 implements StaticMap {
-    private String pattern1;
-    private StaticRoute route1;
-    private String pattern2;
-    private StaticRoute route2;
-    private String pattern3;
-    private StaticRoute route3;
-    private String pattern4;
-    private StaticRoute route4;
-
-    public StaticMap4(StaticMap3 staticMap, String path, StaticRoute route) {
-      this.pattern1 = staticMap.pattern1;
-      this.route1 = staticMap.route1;
-      this.pattern2 = staticMap.pattern2;
-      this.route2 = staticMap.route2;
-      this.pattern3 = staticMap.pattern3;
-      this.route3 = staticMap.route3;
-      this.pattern4 = path;
-      this.route4 = route;
-      staticMap.release();
-    }
-
-    public void release() {
-      this.pattern1 = null;
-      this.route1 = null;
-      this.pattern2 = null;
-      this.route2 = null;
-      this.pattern3 = null;
-      this.route3 = null;
-      this.pattern4 = null;
-      this.route4 = null;
-    }
-
-    @Override
-    public StaticMap put(String path, StaticRoute route) {
-      if (pattern1.equals(path)) {
-        route1 = route;
-        return this;
-      } else if (pattern2.equals(path)) {
-        route2 = route;
-        return this;
-      } else if (pattern3.equals(path)) {
-        route3 = route;
-        return this;
-      } else if (pattern4.equals(path)) {
-        route4 = route;
-        return this;
-      }
-      return new StaticMap5(this, path, route);
-    }
-
-    public StaticRoute get(String path) {
-      if (pattern1.equals(path)) {
-        return route1;
-      } else if (pattern2.equals(path)) {
-        return route2;
-      } else if (pattern3.equals(path)) {
-        return route3;
-      } else if (pattern4.equals(path)) {
-        return route4;
-      } else {
-        return null;
-      }
-    }
-  }
-
-  private static class StaticMap5 implements StaticMap {
-    private String pattern1;
-    private StaticRoute route1;
-    private String pattern2;
-    private StaticRoute route2;
-    private String pattern3;
-    private StaticRoute route3;
-    private String pattern4;
-    private StaticRoute route4;
-    private String pattern5;
-    private StaticRoute route5;
-
-    public StaticMap5(StaticMap4 staticMap, String path, StaticRoute route) {
-      this.pattern1 = staticMap.pattern1;
-      this.route1 = staticMap.route1;
-      this.pattern2 = staticMap.pattern2;
-      this.route2 = staticMap.route2;
-      this.pattern3 = staticMap.pattern3;
-      this.route3 = staticMap.route3;
-      this.pattern4 = staticMap.pattern4;
-      this.route4 = staticMap.route4;
-      this.pattern5 = path;
-      this.route5 = route;
-      staticMap.release();
-    }
-
-    public void release() {
-      this.pattern1 = null;
-      this.route1 = null;
-      this.pattern2 = null;
-      this.route2 = null;
-      this.pattern3 = null;
-      this.route3 = null;
-      this.pattern4 = null;
-      this.route4 = null;
-      this.pattern5 = null;
-      this.route5 = null;
-    }
-
-    @Override
-    public StaticMap put(String path, StaticRoute route) {
-      if (pattern1.equals(path)) {
-        route1 = route;
-        return this;
-      } else if (pattern2.equals(path)) {
-        route2 = route;
-        return this;
-      } else if (pattern3.equals(path)) {
-        route3 = route;
-        return this;
-      } else if (pattern4.equals(path)) {
-        route4 = route;
-        return this;
-      } else if (pattern5.equals(path)) {
-        route5 = route;
-        return this;
-      }
-      return new StaticMap6(this, path, route);
-    }
-
-    public StaticRoute get(String path) {
-      if (pattern1.equals(path)) {
-        return route1;
-      } else if (pattern2.equals(path)) {
-        return route2;
-      } else if (pattern3.equals(path)) {
-        return route3;
-      } else if (pattern4.equals(path)) {
-        return route4;
-      } else if (pattern5.equals(path)) {
-        return route5;
-      } else {
-        return null;
-      }
-    }
-  }
-
-  private static class StaticMap6 implements StaticMap {
-    private String pattern1;
-    private StaticRoute route1;
-    private String pattern2;
-    private StaticRoute route2;
-    private String pattern3;
-    private StaticRoute route3;
-    private String pattern4;
-    private StaticRoute route4;
-    private String pattern5;
-    private StaticRoute route5;
-    private String pattern6;
-    private StaticRoute route6;
-
-    public StaticMap6(StaticMap5 staticMap, String path, StaticRoute route) {
-      this.pattern1 = staticMap.pattern1;
-      this.route1 = staticMap.route1;
-      this.pattern2 = staticMap.pattern2;
-      this.route2 = staticMap.route2;
-      this.pattern3 = staticMap.pattern3;
-      this.route3 = staticMap.route3;
-      this.pattern4 = staticMap.pattern4;
-      this.route4 = staticMap.route4;
-      this.pattern5 = staticMap.pattern5;
-      this.route5 = staticMap.route5;
-      this.pattern6 = path;
-      this.route6 = route;
-      staticMap.release();
-    }
-
-    public void release() {
-      this.pattern1 = null;
-      this.route1 = null;
-      this.pattern2 = null;
-      this.route2 = null;
-      this.pattern3 = null;
-      this.route3 = null;
-      this.pattern4 = null;
-      this.route4 = null;
-      this.pattern5 = null;
-      this.route5 = null;
-      this.pattern6 = null;
-      this.route6 = null;
-    }
-
-    @Override
-    public StaticMap put(String path, StaticRoute route) {
-      if (pattern1.equals(path)) {
-        route1 = route;
-        return this;
-      } else if (pattern2.equals(path)) {
-        route2 = route;
-        return this;
-      } else if (pattern3.equals(path)) {
-        route3 = route;
-        return this;
-      } else if (pattern4.equals(path)) {
-        route4 = route;
-        return this;
-      } else if (pattern5.equals(path)) {
-        route5 = route;
-        return this;
-      } else if (pattern6.equals(path)) {
-        route6 = route;
-      }
-      return new StaticMapN(this, path, route);
-    }
-
-    public StaticRoute get(String path) {
-      if (pattern1.equals(path)) {
-        return route1;
-      } else if (pattern2.equals(path)) {
-        return route2;
-      } else if (pattern3.equals(path)) {
-        return route3;
-      } else if (pattern4.equals(path)) {
-        return route4;
-      } else if (pattern5.equals(path)) {
-        return route5;
-      } else if (pattern6.equals(path)) {
-        return route6;
-      } else {
-        return null;
-      }
-    }
-  }
-
-  private static class StaticMapN implements StaticMap {
-    private final Map<String, StaticRoute> paths = new ConcurrentHashMap<>(10);
-
-    public StaticMapN(StaticMap6 staticMap, String path, StaticRoute staticRoute) {
-      put(staticMap.pattern1, staticMap.route1);
-      put(staticMap.pattern2, staticMap.route2);
-      put(staticMap.pattern3, staticMap.route3);
-      put(staticMap.pattern4, staticMap.route4);
-      put(staticMap.pattern5, staticMap.route5);
-      put(staticMap.pattern6, staticMap.route6);
-      put(path, staticRoute);
-
-      staticMap.release();
-    }
-
-    @Override
-    public StaticRoute get(String path) {
-      return paths.get(path);
-    }
-
-    @Override
-    public StaticMap put(String path, StaticRoute staticRoute) {
-      paths.computeIfAbsent(path, k -> staticRoute);
-      return this;
-    }
-  }
-
   private interface MethodMatcher {
     StaticRouterMatch get(String method);
 
@@ -531,9 +123,6 @@ class Chi implements RouteTree {
 
     @Override
     public StaticRouterMatch get(String method) {
-      if (this.method == method) {
-        return route;
-      }
       return this.method.equals(method) ? route : null;
     }
 
@@ -1206,7 +795,10 @@ class Chi implements RouteTree {
 
   private final Node root = new Node();
 
-  private StaticMap staticPaths = StaticMap.INIT;
+  /**
+   * Not need to use a concurrent map, due we don't allow to add routes after application started.
+   */
+  private final Map<Object, StaticRoute> staticPaths = new ConcurrentHashMap<>();
 
   public void insert(String method, String pattern, Route route) {
     String baseCatchAll = baseCatchAll(pattern);
@@ -1220,8 +812,7 @@ class Chi implements RouteTree {
       pattern = "/*";
     }
     if (Router.pathKeys(pattern).isEmpty()) {
-      StaticRoute staticRoute = new StaticRoute();
-      staticPaths = staticPaths.put(pattern, staticRoute);
+      StaticRoute staticRoute = staticPaths.computeIfAbsent(pattern, k -> new StaticRoute());
       staticRoute.put(method, route);
     }
     root.insertRoute(method, pattern, route);
diff --git a/jooby/src/main/java/io/jooby/internal/Pipeline.java b/jooby/src/main/java/io/jooby/internal/Pipeline.java
index 995f73616..b32b40eef 100644
--- a/jooby/src/main/java/io/jooby/internal/Pipeline.java
+++ b/jooby/src/main/java/io/jooby/internal/Pipeline.java
@@ -20,19 +20,12 @@ import io.jooby.internal.handler.PostDispatchInitializerHandler;
 public class Pipeline {
 
   public static Handler build(
-      boolean requiresDetach,
-      Route route,
-      ExecutionMode mode,
-      Executor executor,
-      ContextInitializer initializer) {
+      Route route, ExecutionMode mode, Executor executor, ContextInitializer initializer) {
     // Set default wrapper and blocking mode
     if (!route.isNonBlockingSet()) {
       route.setNonBlocking(isDefaultNonblocking(executor, mode));
     }
-    Route.Filter wrapper = DEFAULT;
-    if (requiresDetach && route.isNonBlocking()) {
-      wrapper = DETACH.then(wrapper);
-    }
+    Route.Filter wrapper = route.isNonBlocking() ? DETACH : DEFAULT;
 
     // Non-Blocking? Split pipeline Head+Handler let reactive call After pipeline
     Handler pipeline;
diff --git a/jooby/src/main/java/io/jooby/internal/RouterImpl.java b/jooby/src/main/java/io/jooby/internal/RouterImpl.java
index de98cbf49..979915a7f 100644
--- a/jooby/src/main/java/io/jooby/internal/RouterImpl.java
+++ b/jooby/src/main/java/io/jooby/internal/RouterImpl.java
@@ -592,7 +592,7 @@ public class RouterImpl implements Router {
     }
   }
 
-  @NonNull public Router start(@NonNull Jooby app, @NonNull Server server) {
+  @NonNull public Router start(@NonNull Jooby app) {
     started = true;
     if (err == null) {
       err = ErrorHandler.create();
@@ -635,10 +635,9 @@ public class RouterImpl implements Router {
             prependMediaType(route.getConsumes(), route.getFilter(), Route.SUPPORT_MEDIA_TYPE));
         route.setFilter(prependMediaType(route.getProduces(), route.getFilter(), Route.ACCEPT));
       }
-      boolean requiresDetach = server.getName().equals("undertow");
+      /** Response handler: */
       Route.Handler pipeline =
-          Pipeline.build(
-              requiresDetach, route, forceMode(route, mode), executor, postDispatchInitializer);
+          Pipeline.build(route, forceMode(route, mode), executor, postDispatchInitializer);
       route.setPipeline(pipeline);
       /** Final render */
       route.setEncoder(encoder);
diff --git a/jooby/src/main/java/io/jooby/internal/handler/DefaultHandler.java b/jooby/src/main/java/io/jooby/internal/handler/DefaultHandler.java
index 7221e5462..605427209 100644
--- a/jooby/src/main/java/io/jooby/internal/handler/DefaultHandler.java
+++ b/jooby/src/main/java/io/jooby/internal/handler/DefaultHandler.java
@@ -18,11 +18,12 @@ public class DefaultHandler implements Route.Filter {
   public Route.Handler apply(@NonNull Route.Handler next) {
     return ctx -> {
       try {
-        Object value = next.apply(ctx);
-        if (value != ctx && !ctx.isResponseStarted()) {
-          ctx.render(value);
+        Object result = next.apply(ctx);
+        if (ctx.isResponseStarted()) {
+          return result;
         }
-        return value;
+        ctx.render(result);
+        return result;
       } catch (Throwable x) {
         ctx.sendError(x);
         return x;
diff --git a/jooby/src/main/java/io/jooby/internal/handler/DetachHandler.java b/jooby/src/main/java/io/jooby/internal/handler/DetachHandler.java
index 6e7aa31d5..7baea83ad 100644
--- a/jooby/src/main/java/io/jooby/internal/handler/DetachHandler.java
+++ b/jooby/src/main/java/io/jooby/internal/handler/DetachHandler.java
@@ -16,7 +16,20 @@ public class DetachHandler implements Route.Filter {
 
   @NonNull @Override
   public Route.Handler apply(@NonNull Route.Handler next) {
-    return ctx -> ctx.detach(next);
+    return context ->
+        context.detach(
+            (ctx -> {
+              try {
+                Object value = next.apply(ctx);
+                if (value != ctx && !ctx.isResponseStarted()) {
+                  ctx.render(value);
+                }
+                return value;
+              } catch (Throwable cause) {
+                ctx.sendError(cause);
+                return cause;
+              }
+            }));
   }
 
   @Override
diff --git a/jooby/src/main/java/io/jooby/package-info.java b/jooby/src/main/java/io/jooby/package-info.java
deleted file mode 100644
index b10813635..000000000
--- a/jooby/src/main/java/io/jooby/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Welcome to Jooby!
- *
- * <p>Hello World:
- *
- * <pre>{@code
- * public class App extends Jooby {
- *
- *   {
- *     get("/", ctx -> "Hello World!");
- *   }
- *
- *   public static void main(String[] args) {
- *     runApp(args, App::new);
- *   }
- * }
- *
- * }</pre>
- *
- * More documentation at <a href="https://jooby.io">jooby.io</a>
- */
-@edu.umd.cs.findbugs.annotations.ReturnValuesAreNonnullByDefault
-package io.jooby;
diff --git a/modules/jooby-apt/pom.xml b/modules/jooby-apt/pom.xml
index 38333ec26..eeeb872ca 100644
--- a/modules/jooby-apt/pom.xml
+++ b/modules/jooby-apt/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/AnnotationSupport.java b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/AnnotationSupport.java
index 0b46a9ff8..64ca1bf79 100644
--- a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/AnnotationSupport.java
+++ b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/AnnotationSupport.java
@@ -18,7 +18,6 @@ import javax.lang.model.element.VariableElement;
 
 public interface AnnotationSupport {
   Predicate<String> VALUE = "value"::equals;
-  Predicate<String> NAME = "name"::equals;
 
   static List<String> findAnnotationValue(
       AnnotationMirror annotation, Predicate<String> predicate) {
diff --git a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcParameter.java b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcParameter.java
index 8e3697b7c..bfe20898c 100644
--- a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcParameter.java
+++ b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcParameter.java
@@ -5,6 +5,8 @@
  */
 package io.jooby.internal.apt;
 
+import static io.jooby.internal.apt.AnnotationSupport.findAnnotationValue;
+
 import java.util.*;
 import java.util.function.Function;
 import java.util.function.Predicate;
@@ -45,11 +47,21 @@ public class MvcParameter {
                       : Stream.of(Map.entry(found, it.getValue()));
                 })
             .findFirst();
+    var nameProvider =
+        strategy
+            .filter(
+                it ->
+                    !EnumSet.of(ParameterGenerator.BodyParam, ParameterGenerator.Lookup)
+                        .contains(it.getKey()))
+            .map(Map.Entry::getValue)
+            .orElse(null);
     var defaultParameterName = parameter.getSimpleName().toString();
     var parameterName =
-        strategy
-            .map(it -> it.getKey().parameterName(it.getValue(), defaultParameterName))
-            .orElse(defaultParameterName);
+        nameProvider == null
+            ? defaultParameterName
+            : findAnnotationValue(nameProvider, AnnotationSupport.VALUE).stream()
+                .findFirst()
+                .orElse(defaultParameterName);
     var rawType = type.getRawType();
     var elementType =
         type.getArguments().isEmpty() ? rawType : type.getArguments().get(0).getRawType();
diff --git a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRoute.java b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRoute.java
index b0e74a641..995cc6422 100644
--- a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRoute.java
+++ b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/MvcRoute.java
@@ -113,7 +113,7 @@ public class MvcRoute {
                 isSuspendFun() ? "" : "app.",
                 annotation.getSimpleName().toString().toLowerCase(),
                 "(",
-                string(leadingSlash(path)),
+                string(path),
                 ", ",
                 context.pipeline(getReturnTypeHandler(), thisRef + methodName),
                 ")"));
@@ -157,19 +157,6 @@ public class MvcRoute {
     return block;
   }
 
-  /**
-   * Ensure path start with a <code>/</code>(leading slash).
-   *
-   * @param path Path to process.
-   * @return Path with leading slash.
-   */
-  static String leadingSlash(String path) {
-    if (path == null || path.isEmpty() || path.equals("/")) {
-      return "/";
-    }
-    return path.charAt(0) == '/' ? path : "/" + path;
-  }
-
   public List<String> generateHandlerCall(boolean kt) {
     var buffer = new ArrayList<String>();
     /* Parameters */
diff --git a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/ParameterGenerator.java b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/ParameterGenerator.java
index 4776d0428..940dff8f1 100644
--- a/modules/jooby-apt/src/main/java/io/jooby/internal/apt/ParameterGenerator.java
+++ b/modules/jooby-apt/src/main/java/io/jooby/internal/apt/ParameterGenerator.java
@@ -14,7 +14,6 @@ import java.time.Duration;
 import java.time.Period;
 import java.time.ZoneId;
 import java.util.*;
-import java.util.function.Predicate;
 import java.util.stream.Stream;
 
 import javax.lang.model.element.AnnotationMirror;
@@ -52,22 +51,11 @@ public enum ParameterGenerator {
   FlashParam("flash", "io.jooby.annotation.FlashParam"),
   FormParam("form", "io.jooby.annotation.FormParam", "jakarta.ws.rs.FormParam"),
   HeaderParam("header", "io.jooby.annotation.HeaderParam", "jakarta.ws.rs.HeaderParam"),
-  Lookup("lookup", "io.jooby.annotation.Param") {
-    @Override
-    protected Predicate<String> namePredicate() {
-      return AnnotationSupport.NAME;
-    }
-  },
+  Lookup("lookup", "io.jooby.annotation.Param"),
   PathParam("path", "io.jooby.annotation.PathParam", "jakarta.ws.rs.PathParam"),
   QueryParam("query", "io.jooby.annotation.QueryParam", "jakarta.ws.rs.QueryParam"),
   SessionParam("session", "io.jooby.annotation.SessionParam"),
   BodyParam("body") {
-    @Override
-    public String parameterName(AnnotationMirror annotation, String defaultParameterName) {
-      // Body are unnamed
-      return defaultParameterName;
-    }
-
     @Override
     public String toSourceCode(
         boolean kt,
@@ -99,16 +87,6 @@ public enum ParameterGenerator {
     }
   };
 
-  public String parameterName(AnnotationMirror annotation, String defaultParameterName) {
-    return findAnnotationValue(annotation, namePredicate()).stream()
-        .findFirst()
-        .orElse(defaultParameterName);
-  }
-
-  protected Predicate<String> namePredicate() {
-    return AnnotationSupport.VALUE;
-  }
-
   public String toSourceCode(
       boolean kt, AnnotationMirror annotation, TypeDefinition type, String name, boolean nullable) {
     var paramSource = source(annotation);
diff --git a/modules/jooby-apt/src/main/resources/io/jooby/internal/apt/Source.java b/modules/jooby-apt/src/main/resources/io/jooby/internal/apt/Source.java
index ea0f0fabb..db717d65e 100644
--- a/modules/jooby-apt/src/main/resources/io/jooby/internal/apt/Source.java
+++ b/modules/jooby-apt/src/main/resources/io/jooby/internal/apt/Source.java
@@ -16,7 +16,7 @@ public class ${generatedClassName} implements io.jooby.MvcExtension, io.jooby.Mv
         this(ctx -> ctx.require(type));
     }
 
-    public ${generatedClassName}(java.util.function.Supplier<${className}> provider) {
+    public ${generatedClassName}(jakarta.inject.Provider<${className}> provider) {
         this(ctx -> provider.get());
     }
 
@@ -34,7 +34,7 @@ ${methods}
         return type == ${className}.class;
     }
 
-    public io.jooby.Extension create(java.util.function.Supplier<${className}> provider) {
+    public io.jooby.Extension create(jakarta.inject.Provider<${className}> provider) {
         return new ${generatedClassName}(provider);
     }
 }
diff --git a/modules/jooby-apt/src/main/resources/io/jooby/internal/apt/Source.kt b/modules/jooby-apt/src/main/resources/io/jooby/internal/apt/Source.kt
index 4737b60ac..e54a862a7 100644
--- a/modules/jooby-apt/src/main/resources/io/jooby/internal/apt/Source.kt
+++ b/modules/jooby-apt/src/main/resources/io/jooby/internal/apt/Source.kt
@@ -9,7 +9,7 @@ open class ${generatedClassName}(protected val factory: java.util.function.Funct
 
     constructor(type: kotlin.reflect.KClass<${className}>) : this(java.util.function.Function<io.jooby.Context, ${className}> { ctx: io.jooby.Context -> ctx.require<${className}>(type.java) })
 
-    constructor(provider: java.util.function.Supplier<${className}?>) : this(java.util.function.Function<io.jooby.Context, ${className}> { provider.get()!! })
+    constructor(provider: jakarta.inject.Provider<${className}?>) : this(java.util.function.Function<io.jooby.Context, ${className}> { provider.get()!! })
 
     @Throws(Exception::class)
     override fun install(app: io.jooby.Jooby) {
@@ -22,7 +22,7 @@ ${methods}
         return type == ${className}::class.java
     }
 
-    override fun create(provider: java.util.function.Supplier<${className}?>): io.jooby.Extension {
+    override fun create(provider: jakarta.inject.Provider<${className}?>): io.jooby.Extension {
         return ${generatedClassName}(provider)
     }
 }
\ No newline at end of file
diff --git a/modules/jooby-avaje-inject/pom.xml b/modules/jooby-avaje-inject/pom.xml
index 4c4301115..c523929dc 100644
--- a/modules/jooby-avaje-inject/pom.xml
+++ b/modules/jooby-avaje-inject/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
@@ -91,6 +91,11 @@
             <arg>-parameters</arg>
           </compilerArgs>
           <annotationProcessorPaths>
+            <path>
+              <groupId>io.jooby</groupId>
+              <artifactId>jooby-apt</artifactId>
+              <version>${jooby.version}</version>
+            </path>
             <path>
               <groupId>io.avaje</groupId>
               <artifactId>avaje-inject-generator</artifactId>
diff --git a/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/AvajeInjectModule.java b/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/AvajeInjectModule.java
index 119ebee35..be3581015 100644
--- a/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/AvajeInjectModule.java
+++ b/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/AvajeInjectModule.java
@@ -79,7 +79,7 @@ public class AvajeInjectModule implements Extension {
 
     // configuration properties
     final var config = environment.getConfig();
-    beanScope.propertyPlugin(new JoobyPropertyPlugin(config));
+    beanScope.configPlugin(new JoobyPropertyPlugin(config));
 
     for (var entry : config.entrySet()) {
       String name = entry.getKey();
diff --git a/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/JoobyPropertyPlugin.java b/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/JoobyPropertyPlugin.java
index 11318dab1..c01880c50 100644
--- a/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/JoobyPropertyPlugin.java
+++ b/modules/jooby-avaje-inject/src/main/java/io/jooby/avaje/inject/JoobyPropertyPlugin.java
@@ -9,9 +9,10 @@ import java.util.Objects;
 import java.util.Optional;
 
 import com.typesafe.config.Config;
-import io.avaje.inject.spi.PropertyRequiresPlugin;
 
-class JoobyPropertyPlugin implements PropertyRequiresPlugin {
+import io.avaje.inject.spi.ConfigPropertyPlugin;
+
+class JoobyPropertyPlugin implements ConfigPropertyPlugin {
 
   private final Config config;
 
diff --git a/modules/jooby-avaje-jsonb/pom.xml b/modules/jooby-avaje-jsonb/pom.xml
index 466edf380..6ea94200d 100644
--- a/modules/jooby-avaje-jsonb/pom.xml
+++ b/modules/jooby-avaje-jsonb/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-awssdk-v1/pom.xml b/modules/jooby-awssdk-v1/pom.xml
index 6d51fd344..6c59906c6 100644
--- a/modules/jooby-awssdk-v1/pom.xml
+++ b/modules/jooby-awssdk-v1/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-bom/pom.xml b/modules/jooby-bom/pom.xml
index a804c64ed..02c75796b 100644
--- a/modules/jooby-bom/pom.xml
+++ b/modules/jooby-bom/pom.xml
@@ -7,13 +7,13 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <groupId>io.jooby</groupId>
   <artifactId>jooby-bom</artifactId>
   <packaging>pom</packaging>
-  <version>3.2.5-SNAPSHOT</version>
+  <version>3.2.4-SNAPSHOT</version>
   <description>Jooby (Bill of Materials)</description>
   <url>https://jooby.io</url>
 
diff --git a/modules/jooby-caffeine/pom.xml b/modules/jooby-caffeine/pom.xml
index 45deeaddb..f0e0b6273 100644
--- a/modules/jooby-caffeine/pom.xml
+++ b/modules/jooby-caffeine/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-camel/pom.xml b/modules/jooby-camel/pom.xml
index f1e21a233..15b6d628a 100644
--- a/modules/jooby-camel/pom.xml
+++ b/modules/jooby-camel/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-cli/pom.xml b/modules/jooby-cli/pom.xml
index 281be7290..93781e808 100644
--- a/modules/jooby-cli/pom.xml
+++ b/modules/jooby-cli/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-commons-email/pom.xml b/modules/jooby-commons-email/pom.xml
index 76aa5a51b..fa3888417 100644
--- a/modules/jooby-commons-email/pom.xml
+++ b/modules/jooby-commons-email/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-conscrypt/pom.xml b/modules/jooby-conscrypt/pom.xml
index bea14c999..713256e69 100644
--- a/modules/jooby-conscrypt/pom.xml
+++ b/modules/jooby-conscrypt/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-distribution/pom.xml b/modules/jooby-distribution/pom.xml
index 420a2054c..7a7577503 100644
--- a/modules/jooby-distribution/pom.xml
+++ b/modules/jooby-distribution/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-ebean/pom.xml b/modules/jooby-ebean/pom.xml
index 93ee499cc..c115a6661 100644
--- a/modules/jooby-ebean/pom.xml
+++ b/modules/jooby-ebean/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-flyway/pom.xml b/modules/jooby-flyway/pom.xml
index 969db5659..5adf885ca 100644
--- a/modules/jooby-flyway/pom.xml
+++ b/modules/jooby-flyway/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-freemarker/pom.xml b/modules/jooby-freemarker/pom.xml
index 9afb24662..b77a5ade4 100644
--- a/modules/jooby-freemarker/pom.xml
+++ b/modules/jooby-freemarker/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-gradle-setup/pom.xml b/modules/jooby-gradle-setup/pom.xml
index 87f27b832..8be82985b 100644
--- a/modules/jooby-gradle-setup/pom.xml
+++ b/modules/jooby-gradle-setup/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-graphiql/pom.xml b/modules/jooby-graphiql/pom.xml
index 5a4b2595c..251d2d234 100644
--- a/modules/jooby-graphiql/pom.xml
+++ b/modules/jooby-graphiql/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-graphql-playground/pom.xml b/modules/jooby-graphql-playground/pom.xml
index 175b8daa4..22c01be6f 100644
--- a/modules/jooby-graphql-playground/pom.xml
+++ b/modules/jooby-graphql-playground/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-graphql/pom.xml b/modules/jooby-graphql/pom.xml
index 514d8eb2e..e039afed7 100644
--- a/modules/jooby-graphql/pom.xml
+++ b/modules/jooby-graphql/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-gson/pom.xml b/modules/jooby-gson/pom.xml
index ec59b3e2c..6db84a88f 100644
--- a/modules/jooby-gson/pom.xml
+++ b/modules/jooby-gson/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-guice/pom.xml b/modules/jooby-guice/pom.xml
index 28017fa06..e9b3e63e1 100644
--- a/modules/jooby-guice/pom.xml
+++ b/modules/jooby-guice/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-handlebars/pom.xml b/modules/jooby-handlebars/pom.xml
index fbc9b612f..6b63c7c2b 100644
--- a/modules/jooby-handlebars/pom.xml
+++ b/modules/jooby-handlebars/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-hibernate/pom.xml b/modules/jooby-hibernate/pom.xml
index 80b91dd19..caa68ae46 100644
--- a/modules/jooby-hibernate/pom.xml
+++ b/modules/jooby-hibernate/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-hikari/pom.xml b/modules/jooby-hikari/pom.xml
index 7e1e72a36..178645028 100644
--- a/modules/jooby-hikari/pom.xml
+++ b/modules/jooby-hikari/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-jackson/pom.xml b/modules/jooby-jackson/pom.xml
index 3012610b5..ec0ff6ab5 100644
--- a/modules/jooby-jackson/pom.xml
+++ b/modules/jooby-jackson/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-jasypt/pom.xml b/modules/jooby-jasypt/pom.xml
index ff1730b5d..404a9421a 100644
--- a/modules/jooby-jasypt/pom.xml
+++ b/modules/jooby-jasypt/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-jdbi/pom.xml b/modules/jooby-jdbi/pom.xml
index 888e8c957..f26538e10 100644
--- a/modules/jooby-jdbi/pom.xml
+++ b/modules/jooby-jdbi/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-jetty/pom.xml b/modules/jooby-jetty/pom.xml
index 73328dc93..e2fc4d419 100644
--- a/modules/jooby-jetty/pom.xml
+++ b/modules/jooby-jetty/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-jstachio/pom.xml b/modules/jooby-jstachio/pom.xml
index d7218fa39..ffb47c267 100644
--- a/modules/jooby-jstachio/pom.xml
+++ b/modules/jooby-jstachio/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-jte/pom.xml b/modules/jooby-jte/pom.xml
index 2b5c72d4b..71b20a081 100644
--- a/modules/jooby-jte/pom.xml
+++ b/modules/jooby-jte/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-jwt/pom.xml b/modules/jooby-jwt/pom.xml
index 18be01b65..b2aea8e9d 100644
--- a/modules/jooby-jwt/pom.xml
+++ b/modules/jooby-jwt/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-kafka/pom.xml b/modules/jooby-kafka/pom.xml
index 55b9e698e..6a1455590 100644
--- a/modules/jooby-kafka/pom.xml
+++ b/modules/jooby-kafka/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-kotlin/pom.xml b/modules/jooby-kotlin/pom.xml
index 9a4bf7836..3b3e00c17 100644
--- a/modules/jooby-kotlin/pom.xml
+++ b/modules/jooby-kotlin/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-log4j/pom.xml b/modules/jooby-log4j/pom.xml
index 662e17892..09f48d300 100644
--- a/modules/jooby-log4j/pom.xml
+++ b/modules/jooby-log4j/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-logback/pom.xml b/modules/jooby-logback/pom.xml
index 45f641b1f..483c98b95 100644
--- a/modules/jooby-logback/pom.xml
+++ b/modules/jooby-logback/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-maven-plugin/pom.xml b/modules/jooby-maven-plugin/pom.xml
index 1de0f5328..96b3b19d4 100644
--- a/modules/jooby-maven-plugin/pom.xml
+++ b/modules/jooby-maven-plugin/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-metrics/pom.xml b/modules/jooby-metrics/pom.xml
index f2ea4ad00..30d59175a 100644
--- a/modules/jooby-metrics/pom.xml
+++ b/modules/jooby-metrics/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-mutiny/pom.xml b/modules/jooby-mutiny/pom.xml
index 345db333f..2ce31b1a0 100644
--- a/modules/jooby-mutiny/pom.xml
+++ b/modules/jooby-mutiny/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-netty/pom.xml b/modules/jooby-netty/pom.xml
index 6a4280d95..9e234ab4c 100644
--- a/modules/jooby-netty/pom.xml
+++ b/modules/jooby-netty/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/HeadersMultiMap.java b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/HeadersMultiMap.java
index 3308db9fb..71caf5c40 100644
--- a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/HeadersMultiMap.java
+++ b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/HeadersMultiMap.java
@@ -61,20 +61,34 @@ public final class HeadersMultiMap extends HttpHeaders {
     }
   }
 
+  /**
+   * @return a case insensitive multi-map suited for HTTP header validation
+   */
+  public static HeadersMultiMap httpHeaders() {
+    return new HeadersMultiMap(HTTP_VALIDATOR);
+  }
+
   public static HttpHeadersFactory httpHeadersFactory() {
     return new HttpHeadersFactory() {
       @Override
       public HttpHeaders newHeaders() {
-        return new HeadersMultiMap();
+        return HeadersMultiMap.httpHeaders();
       }
 
       @Override
       public HttpHeaders newEmptyHeaders() {
-        return new HeadersMultiMap();
+        return HeadersMultiMap.httpHeaders();
       }
     };
   }
 
+  /**
+   * @return a all-purpose case insensitive multi-map that does not perform validation
+   */
+  public static HeadersMultiMap headers() {
+    return new HeadersMultiMap();
+  }
+
   @Override
   public int size() {
     return names().size();
@@ -85,7 +99,7 @@ public final class HeadersMultiMap extends HttpHeaders {
   private final HeadersMultiMap.MapEntry head = new HeadersMultiMap.MapEntry();
 
   public HeadersMultiMap() {
-    this(HTTP_VALIDATOR);
+    this(null);
   }
 
   public HeadersMultiMap(BiConsumer<CharSequence, CharSequence> validator) {
diff --git a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyContext.java b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyContext.java
index f989c118b..2d287ccfe 100644
--- a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyContext.java
+++ b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyContext.java
@@ -102,7 +102,7 @@ public class NettyContext implements DefaultContext, ChannelFutureListener {
   private static final String STREAM_ID = "x-http2-stream-id";
 
   private String streamId;
-  HeadersMultiMap setHeaders = new HeadersMultiMap();
+  HeadersMultiMap setHeaders = HeadersMultiMap.httpHeaders();
   private int bufferSize;
   InterfaceHttpPostRequestDecoder decoder;
   private Router router;
@@ -145,7 +145,7 @@ public class NettyContext implements DefaultContext, ChannelFutureListener {
     this.req = req;
     this.router = router;
     this.bufferSize = bufferSize;
-    this.method = req.method().name();
+    this.method = req.method().name().toUpperCase();
     if (http2) {
       // Save streamId for HTTP/2
       this.streamId = req.headers().get(STREAM_ID);
diff --git a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyDateService.java b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyDateService.java
deleted file mode 100644
index a7a6cbc6c..000000000
--- a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyDateService.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Jooby https://jooby.io
- * Apache License Version 2.0 https://jooby.io/LICENSE.txt
- * Copyright 2014 Edgar Espina
- */
-package io.jooby.internal.netty;
-
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import io.netty.util.AsciiString;
-
-public class NettyDateService implements Runnable {
-  private static final int DATE_INTERVAL = 1000;
-  private AsciiString date;
-
-  public NettyDateService(ScheduledExecutorService scheduler) {
-    scheduler.scheduleAtFixedRate(this, 0, DATE_INTERVAL, TimeUnit.MILLISECONDS);
-  }
-
-  public AsciiString date() {
-    return this.date;
-  }
-
-  @Override
-  public void run() {
-    this.date =
-        new AsciiString(
-            DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC)));
-  }
-}
diff --git a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyHandler.java b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyHandler.java
index 3512037ee..488879737 100644
--- a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyHandler.java
+++ b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyHandler.java
@@ -9,10 +9,23 @@ import static io.jooby.internal.netty.SlowPathChecks.*;
 import static io.netty.handler.codec.http.HttpUtil.isTransferEncodingChunked;
 
 import java.nio.charset.StandardCharsets;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.slf4j.Logger;
 
-import io.jooby.*;
+import io.jooby.MediaType;
+import io.jooby.Route;
+import io.jooby.Router;
+import io.jooby.Server;
+import io.jooby.SneakyThrows;
+import io.jooby.StatusCode;
+import io.jooby.WebSocketCloseStatus;
 import io.netty.channel.ChannelHandlerContext;
 import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.handler.codec.http.*;
@@ -26,9 +39,13 @@ import io.netty.handler.timeout.IdleStateEvent;
 import io.netty.util.AsciiString;
 
 public class NettyHandler extends ChannelInboundHandlerAdapter {
+  private static final AtomicReference<String> cachedDateString = new AtomicReference<>();
+
+  private static final Runnable INVALIDATE_TASK = () -> cachedDateString.set(null);
+
   private static final AsciiString server = AsciiString.cached("N");
-  private final NettyDateService serverDate;
-  private final Jooby app;
+  private final ScheduledExecutorService scheduler;
+  private static final int DATE_INTERVAL = 1000;
   private final Router router;
   private final int bufferSize;
   private final boolean defaultHeaders;
@@ -40,16 +57,15 @@ public class NettyHandler extends ChannelInboundHandlerAdapter {
   private NettyContext context;
 
   public NettyHandler(
-      NettyDateService dateService,
-      Jooby app,
+      ScheduledExecutorService scheduler,
+      Router router,
       long maxRequestSize,
       int bufferSize,
       HttpDataFactory factory,
       boolean defaultHeaders,
       boolean http2) {
-    this.serverDate = dateService;
-    this.app = app;
-    this.router = app.getRouter();
+    this.scheduler = scheduler;
+    this.router = router;
     this.maxRequestSize = maxRequestSize;
     this.factory = factory;
     this.bufferSize = bufferSize;
@@ -62,10 +78,10 @@ public class NettyHandler extends ChannelInboundHandlerAdapter {
     if (isHttpRequest(msg)) {
       var req = (HttpRequest) msg;
 
-      context = new NettyContext(ctx, req, app, pathOnly(req.uri()), bufferSize, http2);
+      context = new NettyContext(ctx, req, router, pathOnly(req.uri()), bufferSize, http2);
 
       if (defaultHeaders) {
-        context.setHeaders.set(HttpHeaderNames.DATE, serverDate.date());
+        context.setHeaders.set(HttpHeaderNames.DATE, date(router.getLog(), scheduler));
         context.setHeaders.set(HttpHeaderNames.SERVER, server);
       }
       context.setHeaders.set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN);
@@ -145,7 +161,7 @@ public class NettyHandler extends ChannelInboundHandlerAdapter {
   @Override
   public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
     try {
-      Logger log = app.getLog();
+      Logger log = router.getLog();
       if (Server.connectionLost(cause)) {
         if (log.isDebugEnabled()) {
           if (context == null) {
@@ -158,7 +174,7 @@ public class NettyHandler extends ChannelInboundHandlerAdapter {
         if (context == null) {
           log.error("execution resulted in exception", cause);
         } else {
-          if (app.isStopped()) {
+          if (router.isStopped()) {
             log.debug("execution resulted in exception while application was shutting down", cause);
           } else {
             context.sendError(cause);
@@ -219,4 +235,30 @@ public class NettyHandler extends ChannelInboundHandlerAdapter {
       return -1;
     }
   }
+
+  private static String date(Logger log, ScheduledExecutorService scheduler) {
+    var dateString = cachedDateString.get();
+    if (dateString == null) {
+      // set the time and register a timer to invalidate it
+      // note that this is racey, it does not matter if multiple threads do this
+      // the perf cost of synchronizing would be more than the perf cost of multiple threads running
+      // it
+      long realTime = System.currentTimeMillis();
+      long mod = realTime % DATE_INTERVAL;
+      long toGo = DATE_INTERVAL - mod;
+      dateString = DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now(ZoneOffset.UTC));
+      if (cachedDateString.compareAndSet(null, dateString)) {
+        try {
+          scheduler.schedule(INVALIDATE_TASK, toGo, TimeUnit.MILLISECONDS);
+        } catch (RejectedExecutionException rejected) {
+          if (scheduler.isShutdown()) {
+            log.trace("server is shutting down", rejected);
+          } else {
+            throw SneakyThrows.propagate(rejected);
+          }
+        }
+      }
+    }
+    return dateString;
+  }
 }
diff --git a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java
index 09b5b1661..5062a06f6 100644
--- a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java
+++ b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java
@@ -5,9 +5,10 @@
  */
 package io.jooby.internal.netty;
 
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.function.Supplier;
 
-import io.jooby.Jooby;
+import io.jooby.Router;
 import io.jooby.internal.netty.http2.NettyHttp2Configurer;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelOutboundHandler;
@@ -20,10 +21,10 @@ import io.netty.handler.ssl.SslContext;
 public class NettyPipeline extends ChannelInitializer<SocketChannel> {
   private static final String H2_HANDSHAKE = "h2-handshake";
   private final SslContext sslContext;
-  private final NettyDateService serverDate;
+  private final ScheduledExecutorService scheduler;
   private final HttpDataFactory httpDataFactory;
   private final HttpDecoderConfig decoderConfig;
-  private final Jooby router;
+  private final Router router;
   private final long maxRequestSize;
   private final int bufferSize;
   private final boolean defaultHeaders;
@@ -33,10 +34,10 @@ public class NettyPipeline extends ChannelInitializer<SocketChannel> {
 
   public NettyPipeline(
       SslContext sslContext,
-      NettyDateService dateService,
+      ScheduledExecutorService scheduler,
       HttpDataFactory httpDataFactory,
       HttpDecoderConfig decoderConfig,
-      Jooby router,
+      Router router,
       long maxRequestSize,
       int bufferSize,
       boolean defaultHeaders,
@@ -44,7 +45,7 @@ public class NettyPipeline extends ChannelInitializer<SocketChannel> {
       boolean expectContinue,
       Integer compressionLevel) {
     this.sslContext = sslContext;
-    this.serverDate = dateService;
+    this.scheduler = scheduler;
     this.httpDataFactory = httpDataFactory;
     this.decoderConfig = decoderConfig;
     this.router = router;
@@ -58,7 +59,7 @@ public class NettyPipeline extends ChannelInitializer<SocketChannel> {
 
   private NettyHandler createHandler() {
     return new NettyHandler(
-        serverDate, router, maxRequestSize, bufferSize, httpDataFactory, defaultHeaders, http2);
+        scheduler, router, maxRequestSize, bufferSize, httpDataFactory, defaultHeaders, http2);
   }
 
   @Override
@@ -121,7 +122,7 @@ public class NettyPipeline extends ChannelInitializer<SocketChannel> {
   }
 
   private void http11(ChannelPipeline p) {
-    p.addLast("decoder", new NettyRequestDecoder(decoderConfig));
+    p.addLast("decoder", new HttpRequestDecoder(decoderConfig));
     p.addLast("encoder", new NettyResponseEncoder());
     additionalHandlers(p);
     p.addLast("handler", createHandler());
diff --git a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyRequestDecoder.java b/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyRequestDecoder.java
deleted file mode 100644
index f87a24d9d..000000000
--- a/modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyRequestDecoder.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Jooby https://jooby.io
- * Apache License Version 2.0 https://jooby.io/LICENSE.txt
- * Copyright 2014 Edgar Espina
- */
-package io.jooby.internal.netty;
-
-import io.netty.handler.codec.http.*;
-
-public class NettyRequestDecoder extends HttpRequestDecoder {
-
-  public NettyRequestDecoder(HttpDecoderConfig config) {
-    super(config);
-  }
-
-  @Override
-  protected HttpMessage createMessage(String[] initialLine) throws Exception {
-    return new DefaultHttpRequest(
-        HttpVersion.valueOf(initialLine[2]),
-        valueOf(initialLine[0]),
-        initialLine[1],
-        headersFactory);
-  }
-
-  private static HttpMethod valueOf(String name) {
-    // fast-path
-    if (name == HttpMethod.GET.name()) {
-      return HttpMethod.GET;
-    }
-    if (name == HttpMethod.POST.name()) {
-      return HttpMethod.POST;
-    }
-    if (name == HttpMethod.DELETE.name()) {
-      return HttpMethod.DELETE;
-    }
-    if (name == HttpMethod.PUT.name()) {
-      return HttpMethod.PUT;
-    }
-    // "slow"-path: ensure method is on upper case
-    return HttpMethod.valueOf(toUpperCase(name));
-  }
-
-  private static String toUpperCase(String name) {
-    for (int i = 0; i < name.length(); i++) {
-      if (Character.isLowerCase(name.charAt(i))) {
-        return name.toUpperCase();
-      }
-    }
-    return name;
-  }
-}
diff --git a/modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java b/modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java
index 46a6665c1..b95de2576 100644
--- a/modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java
+++ b/modules/jooby-netty/src/main/java/io/jooby/netty/NettyServer.java
@@ -55,9 +55,11 @@ public class NettyServer extends Server.Base {
   private static final int _100 = 100;
 
   private List<Jooby> applications = new ArrayList<>();
+
   private EventLoopGroup acceptorloop;
+
   private EventLoopGroup eventloop;
-  private EventLoopGroup dateLoop;
+
   private ExecutorService worker;
 
   private NettyDataBufferFactory bufferFactory;
@@ -91,6 +93,7 @@ public class NettyServer extends Server.Base {
    * @param bufferFactory Byte buffer allocator.
    */
   public NettyServer(@NonNull NettyDataBufferFactory bufferFactory) {
+    this.worker = null;
     this.bufferFactory = bufferFactory;
   }
 
@@ -143,8 +146,6 @@ public class NettyServer extends Server.Base {
 
       /* Event loop: processing connections, parsing messages and doing engine's internal work */
       this.eventloop = transport.createEventLoop(options.getIoThreads(), "eventloop", _100);
-      this.dateLoop = transport.createEventLoop(1, "date-service", _100);
-      var dateService = new NettyDateService(dateLoop);
 
       /* File data factory: */
       var factory = new DefaultHttpDataFactory(options.getBufferSize());
@@ -152,8 +153,7 @@ public class NettyServer extends Server.Base {
       var http2 = options.isHttp2() == Boolean.TRUE;
       /* Bootstrap: */
       if (!options.isHttpsOnly()) {
-        var http =
-            newBootstrap(allocator, transport, newPipeline(null, factory, dateService, http2));
+        var http = newBootstrap(allocator, transport, newPipeline(null, factory, http2));
         http.bind(options.getHost(), options.getPort()).get();
       }
 
@@ -165,9 +165,7 @@ public class NettyServer extends Server.Base {
 
         var clientAuth = sslOptions.getClientAuth();
         var sslContext = wrap(javaSslContext, toClientAuth(clientAuth), protocol, http2);
-        var https =
-            newBootstrap(
-                allocator, transport, newPipeline(sslContext, factory, dateService, http2));
+        var https = newBootstrap(allocator, transport, newPipeline(sslContext, factory, http2));
         https.bind(options.getHost(), options.getSecurePort()).get();
       } else if (options.isHttpsOnly()) {
         throw new IllegalArgumentException(
@@ -205,8 +203,8 @@ public class NettyServer extends Server.Base {
     };
   }
 
-  private NettyPipeline newPipeline(
-      SslContext sslContext, HttpDataFactory factory, NettyDateService dateService, boolean http2) {
+  private NettyPipeline newPipeline(SslContext sslContext, HttpDataFactory factory, boolean http2) {
+    var executor = acceptorloop.next();
     var router = applications.get(0);
     var bufferSize = options.getBufferSize();
     var headersFactory = HeadersMultiMap.httpHeadersFactory();
@@ -219,7 +217,7 @@ public class NettyServer extends Server.Base {
             .setTrailersFactory(headersFactory);
     return new NettyPipeline(
         sslContext,
-        dateService,
+        executor,
         factory,
         decoderConfig,
         router,
@@ -240,7 +238,6 @@ public class NettyServer extends Server.Base {
 
     shutdown(acceptorloop);
     shutdown(eventloop);
-    shutdown(dateLoop);
     if (worker != null) {
       worker.shutdown();
       worker = null;
@@ -248,9 +245,9 @@ public class NettyServer extends Server.Base {
     return this;
   }
 
-  private void shutdown(EventLoopGroup eventLoopGroup) {
-    if (eventLoopGroup != null) {
-      eventLoopGroup.shutdownGracefully();
+  private void shutdown(EventLoopGroup loopGroup) {
+    if (loopGroup != null) {
+      loopGroup.shutdownGracefully();
     }
   }
 
diff --git a/modules/jooby-node/pom.xml b/modules/jooby-node/pom.xml
index fcea7a230..a86979e75 100644
--- a/modules/jooby-node/pom.xml
+++ b/modules/jooby-node/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-openapi/pom.xml b/modules/jooby-openapi/pom.xml
index 5eda65882..82c8ba503 100644
--- a/modules/jooby-openapi/pom.xml
+++ b/modules/jooby-openapi/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/OpenAPIParser.java b/modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/OpenAPIParser.java
index 63a774c1d..9594c0acc 100644
--- a/modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/OpenAPIParser.java
+++ b/modules/jooby-openapi/src/main/java/io/jooby/internal/openapi/OpenAPIParser.java
@@ -212,17 +212,11 @@ public class OpenAPIParser {
     findAnnotationByType(method.visibleAnnotations, Parameter.class).stream()
         .findFirst()
         .ifPresent(a -> parameters(ctx, operation, singletonList(toMap(a))));
+    /**
+     * @RequestBody:
+     */
     if (method.visibleParameterAnnotations != null) {
       for (List<AnnotationNode> paramAnnotations : method.visibleParameterAnnotations) {
-        /**
-         * @Parameter:
-         */
-        findAnnotationByType(paramAnnotations, Parameter.class).stream()
-            .findFirst()
-            .ifPresent(a -> parameters(ctx, operation, singletonList(toMap(a))));
-        /**
-         * @RequestBody:
-         */
         findAnnotationByType(paramAnnotations, RequestBody.class).stream()
             .findFirst()
             .ifPresent(a -> requestBody(ctx, operation, toMap(a)));
diff --git a/modules/jooby-pac4j/pom.xml b/modules/jooby-pac4j/pom.xml
index a3e049825..87c619de2 100644
--- a/modules/jooby-pac4j/pom.xml
+++ b/modules/jooby-pac4j/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-pebble/pom.xml b/modules/jooby-pebble/pom.xml
index cee94b79b..0c0383058 100644
--- a/modules/jooby-pebble/pom.xml
+++ b/modules/jooby-pebble/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-quartz/pom.xml b/modules/jooby-quartz/pom.xml
index 23225f05f..c0a6fe894 100644
--- a/modules/jooby-quartz/pom.xml
+++ b/modules/jooby-quartz/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-reactor/pom.xml b/modules/jooby-reactor/pom.xml
index ce76dea4f..e4d1da494 100644
--- a/modules/jooby-reactor/pom.xml
+++ b/modules/jooby-reactor/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-redis/pom.xml b/modules/jooby-redis/pom.xml
index bf56882b7..36081fc72 100644
--- a/modules/jooby-redis/pom.xml
+++ b/modules/jooby-redis/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-redoc/pom.xml b/modules/jooby-redoc/pom.xml
index 5f7c355b1..194a6dd4f 100644
--- a/modules/jooby-redoc/pom.xml
+++ b/modules/jooby-redoc/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-rocker/pom.xml b/modules/jooby-rocker/pom.xml
index 389f367e7..4dda6d807 100644
--- a/modules/jooby-rocker/pom.xml
+++ b/modules/jooby-rocker/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-run/pom.xml b/modules/jooby-run/pom.xml
index 0e55fc321..9737cd7d2 100644
--- a/modules/jooby-run/pom.xml
+++ b/modules/jooby-run/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-rxjava3/pom.xml b/modules/jooby-rxjava3/pom.xml
index 83174fcdf..b4103269a 100644
--- a/modules/jooby-rxjava3/pom.xml
+++ b/modules/jooby-rxjava3/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-stork/pom.xml b/modules/jooby-stork/pom.xml
index 34dc8b564..e622a57d2 100644
--- a/modules/jooby-stork/pom.xml
+++ b/modules/jooby-stork/pom.xml
@@ -5,7 +5,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>jooby-stork</artifactId>
diff --git a/modules/jooby-swagger-ui/pom.xml b/modules/jooby-swagger-ui/pom.xml
index 9ba2855bd..a3313afb6 100644
--- a/modules/jooby-swagger-ui/pom.xml
+++ b/modules/jooby-swagger-ui/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-thymeleaf/pom.xml b/modules/jooby-thymeleaf/pom.xml
index 558c337a0..1402b6f7b 100644
--- a/modules/jooby-thymeleaf/pom.xml
+++ b/modules/jooby-thymeleaf/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-undertow/pom.xml b/modules/jooby-undertow/pom.xml
index 657c88cb0..d0f3751e2 100644
--- a/modules/jooby-undertow/pom.xml
+++ b/modules/jooby-undertow/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <properties>
diff --git a/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowContext.java b/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowContext.java
index 16cafbeb6..e6e1fbd3c 100644
--- a/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowContext.java
+++ b/modules/jooby-undertow/src/main/java/io/jooby/internal/undertow/UndertowContext.java
@@ -69,9 +69,14 @@ import io.undertow.server.HttpServerExchange;
 import io.undertow.server.RenegotiationRequiredException;
 import io.undertow.server.SSLSessionInfo;
 import io.undertow.server.handlers.form.FormData;
-import io.undertow.util.*;
+import io.undertow.util.HeaderMap;
+import io.undertow.util.HeaderValues;
+import io.undertow.util.Headers;
+import io.undertow.util.HttpString;
+import io.undertow.util.SameThreadExecutor;
 
 public class UndertowContext implements DefaultContext, IoCallback {
+
   private static final ByteBuffer EMPTY = ByteBuffer.wrap(new byte[0]);
   private Route route;
   HttpServerExchange exchange;
@@ -307,20 +312,18 @@ public class UndertowContext implements DefaultContext, IoCallback {
 
   @NonNull @Override
   public Context detach(@NonNull Route.Handler next) throws Exception {
-    exchange.dispatch(SameThreadExecutor.INSTANCE, detach(this, next));
+    exchange.dispatch(
+        SameThreadExecutor.INSTANCE,
+        () -> {
+          try {
+            next.apply(this);
+          } catch (Throwable cause) {
+            sendError(cause);
+          }
+        });
     return this;
   }
 
-  private static Runnable detach(Context ctx, Route.Handler next) {
-    return () -> {
-      try {
-        next.apply(ctx);
-      } catch (Exception cause) {
-        ctx.sendError(cause);
-      }
-    };
-  }
-
   @NonNull @Override
   public Context upgrade(@NonNull WebSocket.Initializer handler) {
     try {
@@ -571,6 +574,7 @@ public class UndertowContext implements DefaultContext, IoCallback {
   public void onComplete(HttpServerExchange exchange, Sender sender) {
     ifSaveSession();
     sender.close(IoCallback.END_EXCHANGE);
+    //    this.exchange.endExchange();
   }
 
   @NonNull @Override
diff --git a/modules/jooby-whoops/pom.xml b/modules/jooby-whoops/pom.xml
index fdf633187..f30404b18 100644
--- a/modules/jooby-whoops/pom.xml
+++ b/modules/jooby-whoops/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-yasson/pom.xml b/modules/jooby-yasson/pom.xml
index 98d1ebdbd..0e5d9bfc6 100644
--- a/modules/jooby-yasson/pom.xml
+++ b/modules/jooby-yasson/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/pom.xml b/modules/pom.xml
index 4daa20e1c..20c32c9db 100644
--- a/modules/pom.xml
+++ b/modules/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>jooby-project</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <artifactId>modules</artifactId>
diff --git a/pom.xml b/pom.xml
index 70f130978..e79920040 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>io.jooby</groupId>
   <artifactId>jooby-project</artifactId>
-  <version>3.2.5-SNAPSHOT</version>
+  <version>3.2.4-SNAPSHOT</version>
   <packaging>pom</packaging>
 
   <name>jooby-project</name>
@@ -23,7 +23,7 @@
     <handlebars.version>4.4.0</handlebars.version>
     <jstachio.version>1.3.5</jstachio.version>
     <pebble.version>3.2.2</pebble.version>
-    <jackson.version>2.17.2</jackson.version>
+    <jackson.version>2.17.1</jackson.version>
     <gson.version>2.11.0</gson.version>
     <avaje-jsonb.version>1.12</avaje-jsonb.version>
     <jakarta.json.bind-api.version>3.0.1</jakarta.json.bind-api.version>
@@ -47,7 +47,7 @@
     <config.version>1.4.3</config.version>
 
     <!-- dependency injection -->
-    <avaje-inject.version>9.12</avaje-inject.version>
+    <avaje-inject.version>10.0</avaje-inject.version>
     <guice.version>7.0.0</guice.version>
 
     <!-- logging -->
@@ -69,7 +69,7 @@
 
     <!-- Server -->
     <undertow.version>2.3.14.Final</undertow.version>
-    <jetty.version>12.0.11</jetty.version>
+    <jetty.version>12.0.10</jetty.version>
     <netty.version>4.1.111.Final</netty.version>
 
     <!--OpenAPI-->
@@ -92,7 +92,7 @@
     <quartz.version>2.3.2</quartz.version>
     <cron-utils.version>9.2.1</cron-utils.version>
     <bucket4j-core.version>8.0.1</bucket4j-core.version>
-    <aws-java-sdk.version>1.12.757</aws-java-sdk.version>
+    <aws-java-sdk.version>1.12.753</aws-java-sdk.version>
     <camel.version>3.22.2</camel.version>
     <frontend-plugin-core.version>1.15.0</frontend-plugin-core.version>
     <jasypt.version>1.9.3</jasypt.version>
@@ -107,7 +107,7 @@
     <!-- Test dependencies -->
     <jacoco.version>0.8.12</jacoco.version>
     <junit.version>5.10.3</junit.version>
-    <rest-assured.version>5.5.0</rest-assured.version>
+    <rest-assured.version>5.4.0</rest-assured.version>
     <mockito.version>5.12.0</mockito.version>
     <guava.version>33.2.1-jre</guava.version>
 
@@ -165,7 +165,7 @@
     <!--  Javadoc is too strict in 17, don't fail on error -->
     <maven.javadoc.failOnError>false</maven.javadoc.failOnError>
     <maven.build.timestamp.format>yyyy-MM-dd HH:mm:ssa</maven.build.timestamp.format>
-    <project.build.outputTimestamp>2024-07-10T15:42:49Z</project.build.outputTimestamp>
+    <project.build.outputTimestamp>2024-06-24T18:28:54Z</project.build.outputTimestamp>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <timestamp>${maven.build.timestamp}</timestamp>
 


Test patch:
diff --git a/modules/jooby-apt/src/test/java/tests/i3460/C3460.java b/modules/jooby-apt/src/test/java/tests/i3460/C3460.java
deleted file mode 100644
index b79a2c72f..000000000
--- a/modules/jooby-apt/src/test/java/tests/i3460/C3460.java
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Jooby https://jooby.io
- * Apache License Version 2.0 https://jooby.io/LICENSE.txt
- * Copyright 2014 Edgar Espina
- */
-package tests.i3460;
-
-import edu.umd.cs.findbugs.annotations.NonNull;
-import io.jooby.annotation.GET;
-import io.jooby.annotation.Path;
-import io.jooby.annotation.QueryParam;
-
-@Path("/path")
-public class C3460 {
-  @GET("/required-string-param")
-  public String requiredStringParam(@QueryParam("value") @NonNull String value) {
-    return value;
-  }
-}
diff --git a/modules/jooby-apt/src/test/java/tests/i3460/Issue3460.java b/modules/jooby-apt/src/test/java/tests/i3460/Issue3460.java
deleted file mode 100644
index 54828935b..000000000
--- a/modules/jooby-apt/src/test/java/tests/i3460/Issue3460.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Jooby https://jooby.io
- * Apache License Version 2.0 https://jooby.io/LICENSE.txt
- * Copyright 2014 Edgar Espina
- */
-package tests.i3460;
-
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import org.junit.jupiter.api.Test;
-
-import io.jooby.apt.ProcessorRunner;
-
-public class Issue3460 {
-
-  @Test
-  public void shouldNotUseJakartaProvider() throws Exception {
-    new ProcessorRunner(new C3460())
-        .withRouter(
-            (app, source) -> {
-              assertTrue(source.toString().contains("C3460_(java.util.function.Supplier<"));
-            });
-  }
-}
diff --git a/modules/jooby-apt/src/test/java/tests/i3466/C3466.java b/modules/jooby-apt/src/test/java/tests/i3466/C3466.java
deleted file mode 100644
index 9fcde6592..000000000
--- a/modules/jooby-apt/src/test/java/tests/i3466/C3466.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Jooby https://jooby.io
- * Apache License Version 2.0 https://jooby.io/LICENSE.txt
- * Copyright 2014 Edgar Espina
- */
-package tests.i3466;
-
-import java.util.Map;
-import java.util.UUID;
-
-import io.jooby.ParamSource;
-import io.jooby.annotation.*;
-
-@Path("/simple")
-public class C3466 {
-
-  @POST("/test")
-  public String post(
-      @Param(
-              value = {ParamSource.QUERY, ParamSource.FORM},
-              name = "client_id")
-          UUID clientId,
-      Map<String, Object> ignoredBodyName) {
-    return "hello";
-  }
-}
diff --git a/modules/jooby-apt/src/test/java/tests/i3466/Issue3466.java b/modules/jooby-apt/src/test/java/tests/i3466/Issue3466.java
deleted file mode 100644
index 06f4dc974..000000000
--- a/modules/jooby-apt/src/test/java/tests/i3466/Issue3466.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Jooby https://jooby.io
- * Apache License Version 2.0 https://jooby.io/LICENSE.txt
- * Copyright 2014 Edgar Espina
- */
-package tests.i3466;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import org.junit.jupiter.api.Test;
-
-import io.jooby.apt.ProcessorRunner;
-
-public class Issue3466 {
-
-  @Test
-  public void shouldGetNameFromParamLookup() throws Exception {
-    new ProcessorRunner(new C3466())
-        .withRouter(
-            (app, source) -> {
-              assertTrue(
-                  source
-                      .toString()
-                      .contains(
-                          "ctx.lookup(\"client_id\", io.jooby.ParamSource.QUERY,"
-                              + " io.jooby.ParamSource.FORM)"));
-              assertFalse(source.toString().contains("ignoredBodyName"));
-            });
-  }
-}
diff --git a/modules/jooby-apt/src/test/java/tests/i3468/C3468.java b/modules/jooby-apt/src/test/java/tests/i3468/C3468.java
deleted file mode 100644
index 96775487a..000000000
--- a/modules/jooby-apt/src/test/java/tests/i3468/C3468.java
+++ /dev/null
@@ -1,17 +0,0 @@
-/*
- * Jooby https://jooby.io
- * Apache License Version 2.0 https://jooby.io/LICENSE.txt
- * Copyright 2014 Edgar Espina
- */
-package tests.i3468;
-
-import io.jooby.Context;
-import io.jooby.annotation.GET;
-
-public class C3468 {
-
-  @GET("test")
-  public String test(Context ctx) {
-    return ctx.getRequestPath();
-  }
-}
diff --git a/modules/jooby-apt/src/test/java/tests/i3468/Issue3468.java b/modules/jooby-apt/src/test/java/tests/i3468/Issue3468.java
deleted file mode 100644
index 2f4463e62..000000000
--- a/modules/jooby-apt/src/test/java/tests/i3468/Issue3468.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Jooby https://jooby.io
- * Apache License Version 2.0 https://jooby.io/LICENSE.txt
- * Copyright 2014 Edgar Espina
- */
-package tests.i3468;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import org.junit.jupiter.api.Test;
-
-import io.jooby.apt.ProcessorRunner;
-import io.jooby.test.MockContext;
-import io.jooby.test.MockRouter;
-
-public class Issue3468 {
-
-  @Test
-  public void shouldPrefixWithLeadingSlash() throws Exception {
-    new ProcessorRunner(new C3468())
-        .withRouter(
-            (app, source) -> {
-              MockRouter router = new MockRouter(app);
-              MockContext ctx = new MockContext();
-              assertEquals("/test", router.get("/test", ctx).value());
-              assertTrue(source.toString().contains("app.get(\"/test\", this::test)"));
-            });
-  }
-}
diff --git a/modules/jooby-avaje-inject/src/test/java/io/jooby/avaje/inject/AvajeInjectModuleTest.java b/modules/jooby-avaje-inject/src/test/java/io/jooby/avaje/inject/AvajeInjectModuleTest.java
new file mode 100644
index 000000000..aeb91e150
--- /dev/null
+++ b/modules/jooby-avaje-inject/src/test/java/io/jooby/avaje/inject/AvajeInjectModuleTest.java
@@ -0,0 +1,25 @@
+package io.jooby.avaje.inject;
+
+import io.jooby.avaje.inject.app.TestApp;
+import io.jooby.test.JoobyTest;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class AvajeInjectModuleTest {
+
+    static OkHttpClient client = new OkHttpClient();
+
+    @JoobyTest(TestApp.class)
+    public void shouldPropagateJoobyServicesToAvajeBeanScope(String serverPath) throws IOException {
+        Request request = new Request.Builder().url(serverPath + "ping").build();
+
+        try (Response response = client.newCall(request).execute()) {
+            assertEquals("test", response.body().string());
+        }
+    }
+}
diff --git a/modules/jooby-avaje-inject/src/test/java/io/jooby/avaje/inject/app/Controller.java b/modules/jooby-avaje-inject/src/test/java/io/jooby/avaje/inject/app/Controller.java
new file mode 100644
index 000000000..f455f6b2a
--- /dev/null
+++ b/modules/jooby-avaje-inject/src/test/java/io/jooby/avaje/inject/app/Controller.java
@@ -0,0 +1,36 @@
+package io.jooby.avaje.inject.app;
+
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.typesafe.config.Config;
+import io.avaje.inject.InjectModule;
+import io.jooby.annotation.GET;
+import io.jooby.annotation.Path;
+import jakarta.inject.Inject;
+import jakarta.inject.Named;
+import jakarta.inject.Singleton;
+
+@Singleton
+@Path("")
+@InjectModule(requires = {JsonMapper.class, Config.class, String.class})
+public class Controller {
+
+    private final JsonMapper jsonMapper;
+    private final Config config;
+    private final String env;
+
+    @Inject
+    public Controller(JsonMapper jsonMapper, Config config, @Named("application.env") String env) {
+        this.jsonMapper = jsonMapper;
+        this.config = config;
+        this.env = env;
+    }
+
+    @GET
+    @Path("/ping")
+    public String ping() {
+        jsonMapper.version();
+        config.isEmpty();
+
+        return this.env;
+    }
+}
diff --git a/modules/jooby-avaje-inject/src/test/java/io/jooby/avaje/inject/app/TestApp.java b/modules/jooby-avaje-inject/src/test/java/io/jooby/avaje/inject/app/TestApp.java
new file mode 100644
index 000000000..f091511c9
--- /dev/null
+++ b/modules/jooby-avaje-inject/src/test/java/io/jooby/avaje/inject/app/TestApp.java
@@ -0,0 +1,24 @@
+package io.jooby.avaje.inject.app;
+
+import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.typesafe.config.Config;
+import io.jooby.Jooby;
+import io.jooby.avaje.inject.AvajeInjectModule;
+import io.jooby.jackson.JacksonModule;
+
+public class TestApp extends Jooby {
+
+    {
+        install(new JacksonModule());
+        install(AvajeInjectModule.of());
+
+        mvc(Controller.class);
+
+        onStarted(() -> {
+            JsonMapper jsonMapper = require(JsonMapper.class);
+            Config config = require(Config.class);
+            jsonMapper.version();
+            config.isEmpty();
+        });
+    }
+}
diff --git a/modules/jooby-openapi/src/test/java/issues/i3461/App3461.java b/modules/jooby-openapi/src/test/java/issues/i3461/App3461.java
deleted file mode 100644
index 9e8cf3081..000000000
--- a/modules/jooby-openapi/src/test/java/issues/i3461/App3461.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Jooby https://jooby.io
- * Apache License Version 2.0 https://jooby.io/LICENSE.txt
- * Copyright 2014 Edgar Espina
- */
-package issues.i3461;
-
-import java.util.UUID;
-
-import io.jooby.Jooby;
-import io.jooby.annotation.GET;
-import io.jooby.annotation.Path;
-import io.jooby.annotation.QueryParam;
-import io.swagger.v3.oas.annotations.Parameter;
-
-public class App3461 extends Jooby {
-
-  @Path("/")
-  static class Controller {
-
-    @GET("/3461")
-    public String getBlah(
-        @Parameter(required = true, description = "Param annotation") @QueryParam UUID orgId) {
-      return "";
-    }
-  }
-
-  {
-    mvc(new Controller());
-  }
-}
diff --git a/modules/jooby-openapi/src/test/java/issues/i3461/Issue3461.java b/modules/jooby-openapi/src/test/java/issues/i3461/Issue3461.java
deleted file mode 100644
index b85d92628..000000000
--- a/modules/jooby-openapi/src/test/java/issues/i3461/Issue3461.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Jooby https://jooby.io
- * Apache License Version 2.0 https://jooby.io/LICENSE.txt
- * Copyright 2014 Edgar Espina
- */
-package issues.i3461;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import io.jooby.openapi.OpenAPIResult;
-import io.jooby.openapi.OpenAPITest;
-
-public class Issue3461 {
-
-  @OpenAPITest(value = App3461.class)
-  public void shouldParseAvajeBeanScopeControllers(OpenAPIResult result) {
-    assertEquals(
-        "openapi: 3.0.1\n"
-            + "info:\n"
-            + "  title: 3461 API\n"
-            + "  description: 3461 API description\n"
-            + "  version: \"1.0\"\n"
-            + "paths:\n"
-            + "  /3461:\n"
-            + "    get:\n"
-            + "      operationId: getBlah\n"
-            + "      parameters:\n"
-            + "      - name: orgId\n"
-            + "        in: query\n"
-            + "        description: Param annotation\n"
-            + "        required: true\n"
-            + "        schema:\n"
-            + "          type: string\n"
-            + "          format: uuid\n"
-            + "      responses:\n"
-            + "        \"200\":\n"
-            + "          description: Success\n"
-            + "          content:\n"
-            + "            application/json:\n"
-            + "              schema:\n"
-            + "                type: string\n",
-        result.toYaml());
-  }
-}
diff --git a/modules/jooby-test/pom.xml b/modules/jooby-test/pom.xml
index 988af9fb8..541ec9f7b 100644
--- a/modules/jooby-test/pom.xml
+++ b/modules/jooby-test/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>modules</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/modules/jooby-undertow/src/test/java/io/jooby/internal/utow/UndertowFileUploadTest.java b/modules/jooby-undertow/src/test/java/io/jooby/internal/utow/UtowFileUploadTest.java
similarity index 97%
rename from modules/jooby-undertow/src/test/java/io/jooby/internal/utow/UndertowFileUploadTest.java
rename to modules/jooby-undertow/src/test/java/io/jooby/internal/utow/UtowFileUploadTest.java
index f03771ca7..f1631b08c 100644
--- a/modules/jooby-undertow/src/test/java/io/jooby/internal/utow/UndertowFileUploadTest.java
+++ b/modules/jooby-undertow/src/test/java/io/jooby/internal/utow/UtowFileUploadTest.java
@@ -17,7 +17,7 @@ import org.junit.jupiter.api.Test;
 import io.jooby.internal.undertow.UndertowFileUpload;
 import io.undertow.server.handlers.form.FormData;
 
-public class UndertowFileUploadTest {
+public class UtowFileUploadTest {
 
   @Test
   public void shouldGetFileSize() throws IOException {
diff --git a/tests/pom.xml b/tests/pom.xml
index 6915e37a6..676c03f10 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -6,7 +6,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>jooby-project</artifactId>
-    <version>3.2.5-SNAPSHOT</version>
+    <version>3.2.4-SNAPSHOT</version>
   </parent>
 
   <modelVersion>4.0.0</modelVersion>
diff --git a/tests/src/test/java/io/jooby/buffer/LeakAwareDataBufferFactory.java b/tests/src/test/java/io/jooby/buffer/LeakAwareDataBufferFactory.java
index 5872e92dd..ae5bd8824 100644
--- a/tests/src/test/java/io/jooby/buffer/LeakAwareDataBufferFactory.java
+++ b/tests/src/test/java/io/jooby/buffer/LeakAwareDataBufferFactory.java
@@ -101,6 +101,7 @@ public class LeakAwareDataBufferFactory implements DataBufferFactory {
               .map(LeakAwareDataBuffer::leakError)
               .toList();
 
+      errors.forEach(it -> logger.error("Leaked error: ", it));
       throw new AssertionError(errors.size() + " buffer leaks detected (see logs above)");
     }
   }
diff --git a/tests/src/test/java/io/jooby/buffer/LeakAwareDataBufferFactoryTests.java b/tests/src/test/java/io/jooby/buffer/LeakAwareDataBufferFactoryTests.java
index 0b1e6eaa7..6ee78d53f 100644
--- a/tests/src/test/java/io/jooby/buffer/LeakAwareDataBufferFactoryTests.java
+++ b/tests/src/test/java/io/jooby/buffer/LeakAwareDataBufferFactoryTests.java
@@ -6,8 +6,8 @@
 package io.jooby.buffer;
 
 import static io.jooby.buffer.DataBufferUtils.release;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 
-import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -17,12 +17,12 @@ class LeakAwareDataBufferFactoryTests {
 
   private final LeakAwareDataBufferFactory bufferFactory = new LeakAwareDataBufferFactory();
 
-  @Test()
+  @Test
   @SuppressWarnings("deprecation")
   void leak() {
     DataBuffer dataBuffer = this.bufferFactory.allocateBuffer();
     try {
-      Assertions.assertThrows(AssertionError.class, this.bufferFactory::checkForLeaks);
+      assertThatExceptionOfType(AssertionError.class).isThrownBy(this.bufferFactory::checkForLeaks);
     } finally {
       release(dataBuffer);
     }
diff --git a/tests/src/test/java/io/jooby/test/FeaturedTest.java b/tests/src/test/java/io/jooby/test/FeaturedTest.java
index d2d5f5406..bcd0510f5 100644
--- a/tests/src/test/java/io/jooby/test/FeaturedTest.java
+++ b/tests/src/test/java/io/jooby/test/FeaturedTest.java
@@ -10,11 +10,13 @@ import static io.jooby.ExecutionMode.EVENT_LOOP;
 import static io.jooby.ExecutionMode.WORKER;
 import static io.jooby.MediaType.text;
 import static io.jooby.MediaType.xml;
+import static java.util.concurrent.CompletableFuture.supplyAsync;
 import static okhttp3.RequestBody.create;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static reactor.core.scheduler.Schedulers.parallel;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -68,6 +70,7 @@ import io.jooby.InlineFile;
 import io.jooby.Jooby;
 import io.jooby.MessageDecoder;
 import io.jooby.ModelAndView;
+import io.jooby.ReactiveSupport;
 import io.jooby.Router;
 import io.jooby.RouterOption;
 import io.jooby.SameSite;
@@ -89,15 +92,25 @@ import io.jooby.handler.WebVariables;
 import io.jooby.jackson.JacksonModule;
 import io.jooby.junit.ServerTest;
 import io.jooby.junit.ServerTestRunner;
+import io.jooby.mutiny.Mutiny;
 import io.jooby.netty.NettyServer;
+import io.jooby.reactor.Reactor;
 import io.jooby.rxjava3.Reactivex;
 import io.jooby.undertow.UndertowServer;
+import io.reactivex.rxjava3.core.Flowable;
+import io.reactivex.rxjava3.core.Maybe;
+import io.reactivex.rxjava3.core.Observable;
 import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.schedulers.Schedulers;
+import io.smallrye.mutiny.Multi;
+import io.smallrye.mutiny.Uni;
 import okhttp3.FormBody;
 import okhttp3.MediaType;
 import okhttp3.MultipartBody;
 import okhttp3.RequestBody;
 import okhttp3.ResponseBody;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
 
 public class FeaturedTest {
 
@@ -1327,6 +1340,201 @@ public class FeaturedTest {
             });
   }
 
+  @ServerTest
+  public void rx3(ServerTestRunner runner) {
+    runner
+        .define(
+            app -> {
+              app.use(Reactivex.rx());
+              app.get(
+                  "/rx/flowable",
+                  ctx ->
+                      Flowable.range(1, 10)
+                          .map(i -> i + ",")
+                          .subscribeOn(Schedulers.io())
+                          .observeOn(Schedulers.computation()));
+              app.get(
+                  "/rx/observable",
+                  ctx ->
+                      Observable.range(1, 10)
+                          .map(i -> i + ",")
+                          .subscribeOn(Schedulers.io())
+                          .observeOn(Schedulers.computation()));
+              app.get(
+                  "/rx/single",
+                  ctx ->
+                      Single.fromCallable(() -> "Single")
+                          .map(s -> "Hello " + s)
+                          .subscribeOn(Schedulers.io())
+                          .observeOn(Schedulers.computation()));
+              app.get(
+                  "/rx/maybe",
+                  ctx ->
+                      Maybe.fromCallable(() -> "Maybe")
+                          .map(s -> "Hello " + s)
+                          .subscribeOn(Schedulers.io())
+                          .observeOn(Schedulers.computation()));
+
+              app.get("/rx/nomaybe", ctx -> Maybe.empty().subscribeOn(Schedulers.io()));
+
+              app.get(
+                  "/rx/flowable/each",
+                  ctx -> {
+                    Writer writer = ctx.responseWriter();
+                    return Flowable.range(1, 10)
+                        .map(i -> i + ",")
+                        .subscribeOn(Schedulers.io())
+                        .observeOn(Schedulers.computation())
+                        .doOnError(ctx::sendError)
+                        .doFinally(writer::close)
+                        .forEach(
+                            it -> {
+                              writer.write(it);
+                            });
+                  });
+            })
+        .ready(
+            client -> {
+              client.get(
+                  "/rx/flowable",
+                  rsp -> {
+                    assertEquals("chunked", rsp.header("transfer-encoding").toLowerCase());
+                    assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body().string());
+                  });
+              client.get(
+                  "/rx/observable",
+                  rsp -> {
+                    assertEquals("chunked", rsp.header("transfer-encoding").toLowerCase());
+                    assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body().string());
+                  });
+              client.get(
+                  "/rx/single",
+                  rsp -> {
+                    assertEquals("Hello Single", rsp.body().string());
+                  });
+              client.get(
+                  "/rx/maybe",
+                  rsp -> {
+                    assertEquals("Hello Maybe", rsp.body().string());
+                  });
+              client.get(
+                  "/rx/nomaybe",
+                  rsp -> {
+                    assertEquals(404, rsp.code());
+                    assertEquals("", rsp.body().string());
+                  });
+              client.get(
+                  "/rx/flowable/each",
+                  rsp -> {
+                    assertEquals("chunked", rsp.header("transfer-encoding").toLowerCase());
+                    assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body().string());
+                  });
+            });
+  }
+
+  @ServerTest
+  public void reactor(ServerTestRunner runner) {
+    runner
+        .define(
+            app -> {
+              app.use(Reactor.reactor());
+              app.get(
+                  "/reactor/mono",
+                  ctx ->
+                      Mono.fromCallable(() -> "Mono")
+                          .map(s -> "Hello " + s)
+                          .subscribeOn(parallel()));
+              app.get(
+                  "/reactor/flux",
+                  ctx -> Flux.range(1, 10).map(i -> i + ",").subscribeOn(parallel()));
+            })
+        .ready(
+            client -> {
+              client.get(
+                  "/reactor/mono",
+                  rsp -> {
+                    assertEquals("10", rsp.header("content-length").toLowerCase());
+                    assertEquals("Hello Mono", rsp.body().string());
+                  });
+              client.get(
+                  "/reactor/flux",
+                  rsp -> {
+                    assertEquals("chunked", rsp.header("transfer-encoding").toLowerCase());
+                    assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body().string());
+                  });
+            });
+  }
+
+  @ServerTest
+  public void mutiny(ServerTestRunner runner) {
+    runner
+        .define(
+            app -> {
+              app.use(Mutiny.mutiny());
+              app.get(
+                  "/mutiny/uni",
+                  ctx ->
+                      Uni.createFrom()
+                          .completionStage(supplyAsync(() -> "Uni"))
+                          .map(s -> "Hello " + s));
+              app.get("/mutiny/multi", ctx -> Multi.createFrom().range(1, 11).map(i -> i + ","));
+            })
+        .ready(
+            client -> {
+              client.get(
+                  "/mutiny/uni",
+                  rsp -> {
+                    assertEquals("9", rsp.header("content-length").toLowerCase());
+                    assertEquals("Hello Uni", rsp.body().string());
+                  });
+              client.get(
+                  "/mutiny/multi",
+                  rsp -> {
+                    assertEquals("chunked", rsp.header("transfer-encoding").toLowerCase());
+                    assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body().string());
+                  });
+            });
+  }
+
+  @ServerTest
+  public void flow(ServerTestRunner runner) {
+    runner
+        .define(
+            app -> {
+              app.use(ReactiveSupport.concurrent());
+              app.get("/mutiny/multi", ctx -> Multi.createFrom().range(1, 11).map(i -> i + ","));
+            })
+        .ready(
+            client -> {
+              client.get(
+                  "/mutiny/multi",
+                  rsp -> {
+                    assertEquals("chunked", rsp.header("transfer-encoding").toLowerCase());
+                    assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body().string());
+                  });
+            });
+  }
+
+  @ServerTest
+  public void completableFuture(ServerTestRunner runner) {
+    runner
+        .define(
+            app -> {
+              app.use(ReactiveSupport.concurrent());
+              app.get(
+                  "/completable",
+                  ctx -> supplyAsync(() -> "Completable Future!").thenApply(v -> "Hello " + v));
+            })
+        .ready(
+            client -> {
+              client.get(
+                  "/completable",
+                  rsp -> {
+                    assertEquals("Hello Completable Future!", rsp.body().string());
+                  });
+            });
+  }
+
   @ServerTest
   public void contextPath(ServerTestRunner runner) {
     runner
diff --git a/tests/src/test/java/io/jooby/test/ReactiveTest.java b/tests/src/test/java/io/jooby/test/ReactiveTest.java
deleted file mode 100644
index 3e18b5305..000000000
--- a/tests/src/test/java/io/jooby/test/ReactiveTest.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Jooby https://jooby.io
- * Apache License Version 2.0 https://jooby.io/LICENSE.txt
- * Copyright 2014 Edgar Espina
- */
-package io.jooby.test;
-
-import static java.util.concurrent.CompletableFuture.supplyAsync;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static reactor.core.scheduler.Schedulers.parallel;
-
-import java.io.Writer;
-
-import io.jooby.ReactiveSupport;
-import io.jooby.junit.ServerTest;
-import io.jooby.junit.ServerTestRunner;
-import io.jooby.mutiny.Mutiny;
-import io.jooby.reactor.Reactor;
-import io.jooby.rxjava3.Reactivex;
-import io.reactivex.rxjava3.core.Flowable;
-import io.reactivex.rxjava3.core.Maybe;
-import io.reactivex.rxjava3.core.Observable;
-import io.reactivex.rxjava3.core.Single;
-import io.reactivex.rxjava3.schedulers.Schedulers;
-import io.smallrye.mutiny.Multi;
-import io.smallrye.mutiny.Uni;
-import reactor.core.publisher.Flux;
-import reactor.core.publisher.Mono;
-
-public class ReactiveTest {
-  @ServerTest
-  public void rx3(ServerTestRunner runner) {
-    runner
-        .define(
-            app -> {
-              app.use(Reactivex.rx());
-              app.get(
-                  "/rx/flowable",
-                  ctx ->
-                      Flowable.range(1, 10)
-                          .map(i -> i + ",")
-                          .subscribeOn(Schedulers.io())
-                          .observeOn(Schedulers.computation()));
-              app.get(
-                  "/rx/observable",
-                  ctx ->
-                      Observable.range(1, 10)
-                          .map(i -> i + ",")
-                          .subscribeOn(Schedulers.io())
-                          .observeOn(Schedulers.computation()));
-              app.get(
-                  "/rx/single",
-                  ctx ->
-                      Single.fromCallable(() -> "Single")
-                          .map(s -> "Hello " + s)
-                          .subscribeOn(Schedulers.io())
-                          .observeOn(Schedulers.computation()));
-              app.get(
-                  "/rx/maybe",
-                  ctx ->
-                      Maybe.fromCallable(() -> "Maybe")
-                          .map(s -> "Hello " + s)
-                          .subscribeOn(Schedulers.io())
-                          .observeOn(Schedulers.computation()));
-
-              app.get("/rx/nomaybe", ctx -> Maybe.empty().subscribeOn(Schedulers.io()));
-
-              app.get(
-                  "/rx/flowable/each",
-                  ctx -> {
-                    Writer writer = ctx.responseWriter();
-                    return Flowable.range(1, 10)
-                        .map(i -> i + ",")
-                        .subscribeOn(Schedulers.io())
-                        .observeOn(Schedulers.computation())
-                        .doOnError(ctx::sendError)
-                        .doFinally(writer::close)
-                        .forEach(
-                            it -> {
-                              writer.write(it);
-                            });
-                  });
-            })
-        .ready(
-            client -> {
-              client.get(
-                  "/rx/flowable",
-                  rsp -> {
-                    assertEquals("chunked", rsp.header("transfer-encoding").toLowerCase());
-                    assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body().string());
-                  });
-              client.get(
-                  "/rx/observable",
-                  rsp -> {
-                    assertEquals("chunked", rsp.header("transfer-encoding").toLowerCase());
-                    assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body().string());
-                  });
-              client.get(
-                  "/rx/single",
-                  rsp -> {
-                    assertEquals("Hello Single", rsp.body().string());
-                  });
-              client.get(
-                  "/rx/maybe",
-                  rsp -> {
-                    assertEquals("Hello Maybe", rsp.body().string());
-                  });
-              client.get(
-                  "/rx/nomaybe",
-                  rsp -> {
-                    assertEquals(404, rsp.code());
-                    assertEquals("", rsp.body().string());
-                  });
-              client.get(
-                  "/rx/flowable/each",
-                  rsp -> {
-                    assertEquals("chunked", rsp.header("transfer-encoding").toLowerCase());
-                    assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body().string());
-                  });
-            });
-  }
-
-  @ServerTest
-  public void reactor(ServerTestRunner runner) {
-    runner
-        .define(
-            app -> {
-              app.use(Reactor.reactor());
-              app.get(
-                  "/reactor/mono",
-                  ctx ->
-                      Mono.fromCallable(() -> "Mono")
-                          .map(s -> "Hello " + s)
-                          .subscribeOn(parallel()));
-              app.get(
-                  "/reactor/flux",
-                  ctx -> Flux.range(1, 10).map(i -> i + ",").subscribeOn(parallel()));
-            })
-        .ready(
-            client -> {
-              client.get(
-                  "/reactor/mono",
-                  rsp -> {
-                    assertEquals("10", rsp.header("content-length").toLowerCase());
-                    assertEquals("Hello Mono", rsp.body().string());
-                  });
-              client.get(
-                  "/reactor/flux",
-                  rsp -> {
-                    assertEquals("chunked", rsp.header("transfer-encoding").toLowerCase());
-                    assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body().string());
-                  });
-            });
-  }
-
-  @ServerTest
-  public void mutiny(ServerTestRunner runner) {
-    runner
-        .define(
-            app -> {
-              app.use(Mutiny.mutiny());
-              app.get(
-                  "/mutiny/uni",
-                  ctx ->
-                      Uni.createFrom()
-                          .completionStage(supplyAsync(() -> "Uni"))
-                          .map(s -> "Hello " + s));
-              app.get("/mutiny/multi", ctx -> Multi.createFrom().range(1, 11).map(i -> i + ","));
-            })
-        .ready(
-            client -> {
-              client.get(
-                  "/mutiny/uni",
-                  rsp -> {
-                    assertEquals("9", rsp.header("content-length").toLowerCase());
-                    assertEquals("Hello Uni", rsp.body().string());
-                  });
-              client.get(
-                  "/mutiny/multi",
-                  rsp -> {
-                    assertEquals("chunked", rsp.header("transfer-encoding").toLowerCase());
-                    assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body().string());
-                  });
-            });
-  }
-
-  @ServerTest
-  public void flow(ServerTestRunner runner) {
-    runner
-        .define(
-            app -> {
-              app.use(ReactiveSupport.concurrent());
-              app.get("/mutiny/multi", ctx -> Multi.createFrom().range(1, 11).map(i -> i + ","));
-            })
-        .ready(
-            client -> {
-              client.get(
-                  "/mutiny/multi",
-                  rsp -> {
-                    assertEquals("chunked", rsp.header("transfer-encoding").toLowerCase());
-                    assertEquals("1,2,3,4,5,6,7,8,9,10,", rsp.body().string());
-                  });
-            });
-  }
-
-  @ServerTest
-  public void completableFuture(ServerTestRunner runner) {
-    runner
-        .define(
-            app -> {
-              app.use(ReactiveSupport.concurrent());
-              app.get(
-                  "/completable",
-                  ctx -> supplyAsync(() -> "Completable Future!").thenApply(v -> "Hello " + v));
-            })
-        .ready(
-            client -> {
-              client.get(
-                  "/completable",
-                  rsp -> {
-                    assertEquals("Hello Completable Future!", rsp.body().string());
-                  });
-            });
-  }
-}