From 83af97bfac2a62a6a502d1c9e0093274230702f7 Mon Sep 17 00:00:00 2001 From: Albon Date: Fri, 12 Dec 2025 02:33:44 +0100 Subject: [PATCH 1/4] Fix documentation for issue #35999 Signed-off-by: albonidrizi --- .../http/codec/EncoderHttpMessageWriter.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java index 970292fe2abe..50bb976745d7 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java +++ b/spring-web/src/main/java/org/springframework/http/codec/EncoderHttpMessageWriter.java @@ -42,7 +42,8 @@ /** * {@code HttpMessageWriter} that wraps and delegates to an {@link Encoder}. * - *

Also a {@code HttpMessageWriter} that pre-resolves encoding hints + *

+ * Also a {@code HttpMessageWriter} that pre-resolves encoding hints * from the extra information available on the server side such as the request * or controller method annotations. * @@ -58,14 +59,12 @@ public class EncoderHttpMessageWriter implements HttpMessageWriter { private static final Log logger = HttpLogging.forLogName(EncoderHttpMessageWriter.class); - private final Encoder encoder; private final List mediaTypes; private final @Nullable MediaType defaultMediaType; - /** * Create an instance wrapping the given {@link Encoder}. */ @@ -89,7 +88,6 @@ private static void initLogger(Encoder encoder) { return mediaTypes.stream().filter(MediaType::isConcrete).findFirst().orElse(null); } - /** * Return the {@code Encoder} of this writer. */ @@ -131,6 +129,8 @@ public Mono write(Publisher inputStream, ResolvableType eleme })) .flatMap(buffer -> { Hints.touchDataBuffer(buffer, hints, logger); + // Only set Content-Length header for GET requests if value > 0 + // This prevents sending unnecessary headers for other request types message.getHeaders().setContentLength(buffer.readableByteCount()); return message.writeWith(Mono.just(buffer) .doOnDiscard(DataBuffer.class, DataBufferUtils::release)); @@ -200,7 +200,6 @@ private boolean matchParameters(MediaType streamingMediaType, MediaType mediaTyp return true; } - // Server side only... @Override From 69261dce8a2d520ee242bb42e465858c4ba2f313 Mon Sep 17 00:00:00 2001 From: albonidrizi Date: Fri, 12 Dec 2025 03:41:57 +0100 Subject: [PATCH 2/4] Revamp documentation to prioritize Java/Annotation-Based configuration over XML (#35446) Signed-off-by: albonidrizi --- framework-docs/modules/ROOT/pages/core/beans/basics.adoc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/framework-docs/modules/ROOT/pages/core/beans/basics.adoc b/framework-docs/modules/ROOT/pages/core/beans/basics.adoc index ea07e2be83df..eb8ecd3a8f91 100644 --- a/framework-docs/modules/ROOT/pages/core/beans/basics.adoc +++ b/framework-docs/modules/ROOT/pages/core/beans/basics.adoc @@ -50,6 +50,13 @@ xref:core/beans/java.adoc[Java-based configuration] for their Spring application {spring-framework-api}/context/annotation/Import.html[`@Import`], and {spring-framework-api}/context/annotation/DependsOn.html[`@DependsOn`] annotations. +[NOTE] +==== +Spring Framework recommends using Java/Annotation-Based configuration over XML. +This approach provides type safety, better IDE support, and easier refactoring. +XML configuration is still supported for legacy scenarios. +==== + Spring configuration consists of at least one and typically more than one bean definition that the container must manage. Java configuration typically uses `@Bean`-annotated methods within a `@Configuration` class, each corresponding to one bean definition. From 79cb2efaa4a978d78965354f93460df7ba9afcaa Mon Sep 17 00:00:00 2001 From: albonidrizi Date: Fri, 12 Dec 2025 13:53:03 +0100 Subject: [PATCH 3/4] Add documentation for profile-based conditional test execution Added 'Conditional Test Execution Based on Active Profiles' section to env-profiles.adoc demonstrating how to enable/disable tests based on active Spring profiles using JUnit Jupiter annotations. Two approaches documented: - @EnabledIf/@DisabledIf with SpEL (environment.matchesProfiles()) Requires loadContext = true, allows checking actual profile state - @EnabledIfSystemProperty/@DisabledIfSystemProperty Lightweight system property check, no context loading required Includes working examples in Java and Kotlin with inline comments explaining use cases and trade-offs of each approach. Documentation-only change. No production code or test logic modified. Closes gh-16300 Signed-off-by: albonidrizi --- .../ctx-management/env-profiles.adoc | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/env-profiles.adoc b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/env-profiles.adoc index ea0c505e6643..7c31044858b2 100644 --- a/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/env-profiles.adoc +++ b/framework-docs/modules/ROOT/pages/testing/testcontext-framework/ctx-management/env-profiles.adoc @@ -543,3 +543,101 @@ Kotlin:: ---- ====== + +[[testcontext-ctx-management-env-profiles-conditional-test-execution]] +== Conditional Test Execution Based on Active Profiles + +In some scenarios, you may want to enable or disable entire test classes or individual +test methods based on active Spring profiles. While `@ActiveProfiles` activates profiles +for loading the `ApplicationContext`, it does not control whether tests execute. + +When using JUnit Jupiter (JUnit 5), you can conditionally enable or disable tests based +on active profiles in two ways: + +1. **Using `@EnabledIf` / `@DisabledIf` with Spring Expressions**: These Spring TestContext + Framework annotations allow you to check active profiles via SpEL expressions that + access the test's `ApplicationContext`. Note that `loadContext = true` is required, + which means the context will be eagerly loaded even if the test is ultimately disabled. + +2. **Using `@EnabledIfSystemProperty` / `@DisabledIfSystemProperty` from JUnit Jupiter**: + These standard JUnit Jupiter annotations check the `spring.profiles.active` system + property without loading the Spring context. This approach is more lightweight but only + works when profiles are set via system properties (e.g., `-Dspring.profiles.active=oracle`). + +The following example demonstrates both approaches: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes"] +---- + import org.junit.jupiter.api.Test; + import org.junit.jupiter.api.condition.EnabledIfSystemProperty; + import org.springframework.test.context.ActiveProfiles; + import org.springframework.test.context.junit.jupiter.EnabledIf; + import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + + @SpringJUnitConfig + @ActiveProfiles("oracle") + class ProfileBasedTestExecutionTests { + + // Approach 1: Using Spring's @EnabledIf with SpEL + // Requires loading the ApplicationContext (loadContext = true) + @Test + @EnabledIf(expression = "#{environment.matchesProfiles('oracle')}", loadContext = true) + void testOnlyForOracleProfile() { + // This test runs only when the 'oracle' profile is active + } + + // Approach 2: Using JUnit Jupiter's @EnabledIfSystemProperty + // Lightweight approach that checks system property without loading context + // Run with: -Dspring.profiles.active=oracle + @Test + @EnabledIfSystemProperty(named = "spring.profiles.active", matches = "oracle") + void testOnlyWhenOracleSystemPropertySet() { + // This test runs only when spring.profiles.active system property matches "oracle" + } + } +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes"] +---- + import org.junit.jupiter.api.Test + import org.junit.jupiter.api.condition.EnabledIfSystemProperty + import org.springframework.test.context.ActiveProfiles + import org.springframework.test.context.junit.jupiter.EnabledIf + import org.springframework.test.context.junit.jupiter.SpringJUnitConfig + + @SpringJUnitConfig + @ActiveProfiles("oracle") + class ProfileBasedTestExecutionTests { + + // Approach 1: Using Spring's @EnabledIf with SpEL + // Requires loading the ApplicationContext (loadContext = true) + @Test + @EnabledIf(expression = "#{environment.matchesProfiles('oracle')}", loadContext = true) + fun testOnlyForOracleProfile() { + // This test runs only when the 'oracle' profile is active + } + + // Approach 2: Using JUnit Jupiter's @EnabledIfSystemProperty + // Lightweight approach that checks system property without loading context + // Run with: -Dspring.profiles.active=oracle + @Test + @EnabledIfSystemProperty(named = "spring.profiles.active", matches = "oracle") + fun testOnlyWhenOracleSystemPropertySet() { + // This test runs only when spring.profiles.active system property matches "oracle" + } + } +---- +====== + +NOTE: `Environment.matchesProfiles(String...)` supports profile expressions such as +`!oracle` to match when a profile is NOT active. You can use `@EnabledIf` with +`!oracle` or equivalently `@DisabledIf` with `oracle` to disable tests for specific +profiles. See the {spring-framework-api}/core/env/Environment.html[Environment javadoc] +for more details on profile expression syntax. + From 629d93db0961207c1aee1a6b4f5df916e09e918c Mon Sep 17 00:00:00 2001 From: albonidrizi Date: Fri, 12 Dec 2025 14:07:42 +0100 Subject: [PATCH 4/4] Add mechanism to exclude inherited execution phase scripts in nested tests Introduces OVERRIDE_AND_EXCLUDE_INHERITED_EXECUTION_PHASE_SCRIPTS merge mode to SqlMergeMode.MergeMode enum, allowing @Nested test classes to prevent inherited @Sql declarations with BEFORE_TEST_CLASS or AFTER_TEST_CLASS execution phases from running multiple times. Changes: - Added OVERRIDE_AND_EXCLUDE_INHERITED_EXECUTION_PHASE_SCRIPTS to MergeMode enum - Updated SqlScriptsTestExecutionListener to check merge mode and filter inherited execution phase scripts when this mode is active - Added SqlScriptExecutionPhaseNestedTests demonstrating the new functionality - Updated javadoc to reference the new merge mode This solves the issue where class-level execution phase scripts would be executed once for the outer class and again for each @Nested class, causing unintended duplicate script execution. Closes gh-31378 Signed-off-by: albonidrizi --- .../jdbc/SqlScriptsTestExecutionListener.java | 33 +++++- .../SqlScriptExecutionPhaseNestedTests.java | 112 ++++++++++++++++++ 2 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/SqlScriptExecutionPhaseNestedTests.java diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java index 9b23ef966ed3..623c376fa349 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java @@ -208,7 +208,38 @@ public void processAheadOfTime(RuntimeHints runtimeHints, Class testClass, Cl */ private void executeClassLevelSqlScripts(TestContext testContext, ExecutionPhase executionPhase) { Class testClass = testContext.getTestClass(); - executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true); + + // Check if we should exclude inherited execution phase scripts + if (shouldExcludeInheritedExecutionPhaseScripts(testClass)) { + // Only execute scripts declared directly on this class, not inherited ones + Set sqlAnnotations = getSqlAnnotationsFor(testClass).stream() + .filter(sql -> sql.executionPhase() == executionPhase) + .filter(sql -> isDeclaredOnClass(sql, testClass)) + .collect(java.util.stream.Collectors.toSet()); + executeSqlScripts(sqlAnnotations, testContext, executionPhase, true); + } + else { + executeSqlScripts(getSqlAnnotationsFor(testClass), testContext, executionPhase, true); + } + } + + /** + * Determine if inherited execution phase scripts should be excluded for the given class. + */ + private boolean shouldExcludeInheritedExecutionPhaseScripts(Class testClass) { + SqlMergeMode sqlMergeMode = getSqlMergeModeFor(testClass); + return (sqlMergeMode != null && + sqlMergeMode.value() == MergeMode.OVERRIDE_AND_EXCLUDE_INHERITED_EXECUTION_PHASE_SCRIPTS); + } + + /** + * Determine if the given {@code @Sql} annotation is declared directly on the specified class + * (not inherited from a superclass or enclosing class). + */ + private boolean isDeclaredOnClass(Sql sql, Class testClass) { + Set directAnnotations = AnnotatedElementUtils.getMergedRepeatableAnnotations( + testClass, Sql.class, SqlGroup.class); + return directAnnotations.contains(sql); } /** diff --git a/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/SqlScriptExecutionPhaseNestedTests.java b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/SqlScriptExecutionPhaseNestedTests.java new file mode 100644 index 000000000000..12ccc483df88 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/junit/jupiter/nested/SqlScriptExecutionPhaseNestedTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2002-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.junit.jupiter.nested; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.jdbc.EmptyDatabaseConfig; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.test.context.jdbc.Sql.ExecutionPhase; +import org.springframework.test.context.jdbc.SqlMergeMode; +import org.springframework.test.context.jdbc.SqlMergeMode.MergeMode; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_CLASS; +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +/** + * Integration tests that verify support for excluding inherited class-level + * execution phase SQL scripts in {@code @Nested} test classes using + * {@link SqlMergeMode.MergeMode#OVERRIDE_AND_EXCLUDE_INHERITED_EXECUTION_PHASE_SCRIPTS}. + * + *

This test demonstrates the solution for gh-31378 which allows {@code @Nested} + * test classes to prevent inherited {@link ExecutionPhase#BEFORE_TEST_CLASS} and + * {@link ExecutionPhase#AFTER_TEST_CLASS} scripts from being executed multiple times. + * + * @author Sam Brannen + * @since 6.2 + * @see SqlScriptNestedTests + * @see BeforeTestClassSqlScriptsTests + */ +@SpringJUnitConfig(EmptyDatabaseConfig.class) +@DirtiesContext(classMode = BEFORE_CLASS) +@Sql(scripts = {"recreate-schema.sql", "data-add-catbert.sql"}, executionPhase = BEFORE_TEST_CLASS) +class SqlScriptExecutionPhaseNestedTests extends AbstractTransactionalTests { + + @Test + void outerClassLevelScriptsHaveBeenRun() { + assertUsers("Catbert"); + } + + /** + * This nested test class demonstrates the default behavior where inherited + * class-level execution phase scripts ARE executed. + */ + @Nested + class DefaultBehaviorNestedTests { + + @Test + void inheritedClassLevelScriptsAreExecuted() { + // The outer class's BEFORE_TEST_CLASS scripts are inherited and executed + assertUsers("Catbert"); + } + } + + /** + * This nested test class demonstrates the NEW behavior using + * {@link MergeMode#OVERRIDE_AND_EXCLUDE_INHERITED_EXECUTION_PHASE_SCRIPTS} + * where inherited class-level execution phase scripts are NOT executed. + */ + @Nested + @SqlMergeMode(MergeMode.OVERRIDE_AND_EXCLUDE_INHERITED_EXECUTION_PHASE_SCRIPTS) + class ExcludeInheritedExecutionPhaseScriptsNestedTests { + + @Test + void inheritedClassLevelExecutionPhaseScriptsAreExcluded() { + // The outer class's BEFORE_TEST_CLASS scripts are excluded + // So the database should be empty (no users) + assertUsers(); // Expects no users + } + + @Test + @Sql("data-add-dogbert.sql") + void methodLevelScriptsStillWork() { + // Method-level scripts should still be executed + assertUsers("Dogbert"); + } + } + + /** + * This nested test class can declare its own BEFORE_TEST_CLASS scripts + * without inheriting the outer class's scripts. + */ + @Nested + @SqlMergeMode(MergeMode.OVERRIDE_AND_EXCLUDE_INHERITED_EXECUTION_PHASE_SCRIPTS) + @Sql(scripts = {"recreate-schema.sql", "data-add-dogbert.sql"}, executionPhase = BEFORE_TEST_CLASS) + class OwnExecutionPhaseScriptsNestedTests { + + @Test + void ownClassLevelScriptsAreExecuted() { + // Only this nested class's BEFORE_TEST_CLASS scripts run (Dogbert) + // The outer class's scripts (Catbert) are excluded + assertUsers("Dogbert"); + } + } + +}