From d645788d47980c4d70d3831c0372dfd684ebab7a Mon Sep 17 00:00:00 2001 From: arimu1 <19286898+arimu1@users.noreply.github.com> Date: Sat, 12 Sep 2026 19:55:26 +0700 Subject: [PATCH 1/2] fix(server): init apps after server options are set Call server.init from runApp only after final ServerOptions are applied, and from JoobyExtension before start, so the service registry does not retain stale defaults from bootstrap. Fixes #3995 --- jooby/src/main/java/io/jooby/Jooby.java | 6 +-- jooby/src/test/java/io/jooby/Issue3995.java | 54 +++++++++++++++++++ .../test/java/io/jooby/JoobyRunnerTest.java | 12 +++-- .../java/io/jooby/test/JoobyExtension.java | 1 + 4 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 jooby/src/test/java/io/jooby/Issue3995.java diff --git a/jooby/src/main/java/io/jooby/Jooby.java b/jooby/src/main/java/io/jooby/Jooby.java index 3040d2ac47..77d758880d 100644 --- a/jooby/src/main/java/io/jooby/Jooby.java +++ b/jooby/src/main/java/io/jooby/Jooby.java @@ -131,9 +131,6 @@ public Jooby() { } else { copyState(owner, this); } - if (BOOT_SERVER != null) { - BOOT_SERVER.init(this); - } } @Override @@ -1242,6 +1239,9 @@ public static void runApp( if (server.getOptions().defaults && appServerOptions != null) { server.setOptions(appServerOptions); } + for (var app : apps) { + server.init(app); + } targetServer.start(apps.toArray(new Jooby[0])); } catch (Throwable startupError) { try { diff --git a/jooby/src/test/java/io/jooby/Issue3995.java b/jooby/src/test/java/io/jooby/Issue3995.java new file mode 100644 index 0000000000..2c9e232bf2 --- /dev/null +++ b/jooby/src/test/java/io/jooby/Issue3995.java @@ -0,0 +1,54 @@ +/* + * Jooby https://jooby.io + * Apache License Version 2.0 https://jooby.io/LICENSE.txt + * Copyright 2014 Edgar Espina + */ +package io.jooby; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.junit.jupiter.api.Test; + +import io.jooby.output.OutputFactory; + +class Issue3995 { + + private static class TestServer extends Server.Base { + @Override + public String getName() { + return "test-server"; + } + + @Override + public OutputFactory getOutputFactory() { + return OutputFactory.create(); + } + + @Override + public Server start(Jooby... application) { + return this; + } + + @Override + public Server stop() { + return this; + } + } + + @Test + void serverInitMustRunAfterFinalServerOptionsAreSet() { + var server = new TestServer(); + var configured = new ServerOptions().setPort(3995); + + Jooby app = Jooby.createApp(server, ExecutionMode.DEFAULT, () -> new Jooby() {}); + + server.setOptions(configured); + server.init(app); + + var registryOptions = app.getServices().require(ServerOptions.class); + assertSame(configured, registryOptions); + assertEquals(3995, registryOptions.getPort()); + assertEquals("test-server", registryOptions.getServer()); + } +} diff --git a/jooby/src/test/java/io/jooby/JoobyRunnerTest.java b/jooby/src/test/java/io/jooby/JoobyRunnerTest.java index 4f22789052..616fddb090 100644 --- a/jooby/src/test/java/io/jooby/JoobyRunnerTest.java +++ b/jooby/src/test/java/io/jooby/JoobyRunnerTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.InOrder; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -93,8 +94,11 @@ void testRunApp_MultipleApps_MutedServer_DefaultsTrue() { // Verification // Defaults was true, so server.setOptions should be called with appOptions - verify(server).setOptions(appOptions); - verify(mutedServer).start(new Jooby[] {app1, app2}); + InOrder inOrder = inOrder(server, mutedServer); + inOrder.verify(server).setOptions(appOptions); + inOrder.verify(server).init(app1); + inOrder.verify(server).init(app2); + inOrder.verify(mutedServer).start(new Jooby[] {app1, app2}); } @Test @@ -124,7 +128,9 @@ void testRunApp_SingleApp_NormalServer_DefaultsFalse() { // Verification verify(server, never()).setOptions(any()); // Because defaults == false - verify(server).start(new Jooby[] {app1}); + InOrder inOrder = inOrder(server); + inOrder.verify(server).init(app1); + inOrder.verify(server).start(new Jooby[] {app1}); } @DisplayName("Test Exception: StartupException thrown, stop throws ignored exception") diff --git a/modules/jooby-test/src/main/java/io/jooby/test/JoobyExtension.java b/modules/jooby-test/src/main/java/io/jooby/test/JoobyExtension.java index 1d5c4fb6d1..bcd53d548c 100644 --- a/modules/jooby-test/src/main/java/io/jooby/test/JoobyExtension.java +++ b/modules/jooby-test/src/main/java/io/jooby/test/JoobyExtension.java @@ -77,6 +77,7 @@ private Jooby startApp(ExtensionContext context, JoobyTest metadata) throws Exce var defaultEnv = System.getProperty("application.env"); System.setProperty("application.env", metadata.environment()); app = Jooby.createApp(server, metadata.executionMode(), reflectionProvider(metadata.value())); + server.init(app); server.start(app); if (defaultEnv != null) { System.setProperty("application.env", defaultEnv); From f8c85100ff1c50a1ea39690abd2a4d9152b12331 Mon Sep 17 00:00:00 2001 From: arimu1 <19286898+arimu1@users.noreply.github.com> Date: Sat, 12 Sep 2026 20:03:28 +0700 Subject: [PATCH 2/2] fix(server): init in ServerTestRunner and factory Jooby tests Call server.init after test server options are finalized and before start for integration tests that no longer rely on Jooby constructor init. --- .../jooby-test/src/main/java/io/jooby/test/JoobyExtension.java | 1 + tests/src/test/java/io/jooby/junit/ServerTestRunner.java | 1 + 2 files changed, 2 insertions(+) diff --git a/modules/jooby-test/src/main/java/io/jooby/test/JoobyExtension.java b/modules/jooby-test/src/main/java/io/jooby/test/JoobyExtension.java index bcd53d548c..93624d75e3 100644 --- a/modules/jooby-test/src/main/java/io/jooby/test/JoobyExtension.java +++ b/modules/jooby-test/src/main/java/io/jooby/test/JoobyExtension.java @@ -86,6 +86,7 @@ private Jooby startApp(ExtensionContext context, JoobyTest metadata) throws Exce } } else { app = fromFactoryMethod(context, metadata, factoryMethod); + server.init(app); server.start(app); } ExtensionContext.Store store = getStore(context); diff --git a/tests/src/test/java/io/jooby/junit/ServerTestRunner.java b/tests/src/test/java/io/jooby/junit/ServerTestRunner.java index 256e2bd6e8..a92f7ab3b6 100644 --- a/tests/src/test/java/io/jooby/junit/ServerTestRunner.java +++ b/tests/src/test/java/io/jooby/junit/ServerTestRunner.java @@ -106,6 +106,7 @@ public void ready(SneakyThrows.Consumer2 onReady) { if (options.isSSLEnabled()) { options.setSecurePort(0); } + server.init(app); WebClient https = null; try { MutedServer.mute(server).start(app);