diff --git a/build.gradle b/build.gradle
index d01d0d701ba..b2e7af70c62 100644
--- a/build.gradle
+++ b/build.gradle
@@ -110,7 +110,7 @@ configure(allprojects.findAll{it.name in ["spring", "spring-jms", "spring-orm",
 	}
 }
 
-configure(subprojects) { subproject ->
+configure(subprojects - project(":spring-build-junit")) { subproject ->
 	apply plugin: "merge"
 	apply from: "${gradleScriptDir}/publish-maven.gradle"
 
@@ -159,6 +159,34 @@ configure(subprojects) { subproject ->
 	}
 }
 
+configure(allprojects - project(":spring-build-junit")) {
+	dependencies {
+		testCompile(project(":spring-build-junit"))
+	}
+
+	eclipse.classpath.file.whenMerged { classpath ->
+		classpath.entries.find{it.path == "/spring-build-junit"}.exported = false
+	}
+
+	test.systemProperties.put("testGroups", properties.get("testGroups"))
+}
+
+project("spring-build-junit") {
+	description = "Build-time JUnit dependencies and utilities"
+
+	// NOTE: This is an internal project and is not published.
+
+	dependencies {
+		compile("commons-logging:commons-logging:1.1.1")
+		compile("junit:junit:${junitVersion}")
+		compile("org.hamcrest:hamcrest-all:1.3")
+		compile("org.easymock:easymock:${easymockVersion}")
+	}
+
+	// Don't actually generate any artifacts
+	configurations.archives.artifacts.clear()
+}
+
 
 project("spring-core") {
 	description = "Spring Core"
diff --git a/settings.gradle b/settings.gradle
index 11ce95641fb..8d4e147bccb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -22,3 +22,4 @@ include "spring-web"
 include "spring-webmvc"
 include "spring-webmvc-portlet"
 include "spring-webmvc-tiles3"
+include "spring-build-junit"
diff --git a/spring-build-junit/src/main/java/org/springframework/build/junit/Assume.java b/spring-build-junit/src/main/java/org/springframework/build/junit/Assume.java
new file mode 100644
index 00000000000..7b916d0b59c
--- /dev/null
+++ b/spring-build-junit/src/main/java/org/springframework/build/junit/Assume.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2002-2012 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
+ *
+ *      http://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.build.junit;
+
+import static org.junit.Assume.assumeFalse;
+
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.junit.internal.AssumptionViolatedException;
+
+/**
+ * Provides utility methods that allow JUnit tests to {@link Assume} certain conditions
+ * hold {@code true}. If the assumption fails, it means the test should be skipped.
+ *
+ * 
For example, if a set of tests require at least JDK 1.7 it can use
+ * {@code Assume#atLeast(JdkVersion.JAVA_17)} as shown below:
+ *
+ * 
+ * public void MyTests {
+ *
+ *   @BeforeClass
+ *   public static assumptions() {
+ *       Assume.atLeast(JdkVersion.JAVA_17);
+ *   }
+ *
+ *   // ... all the test methods that require at least JDK 1.7
+ * }
+ * 
+ *
+ * If only a single test requires at least JDK 1.7 it can use the
+ * {@code Assume#atLeast(JdkVersion.JAVA_17)} as shown below:
+ *
+ * 
+ * public void MyTests {
+ *
+ *   @Test
+ *   public void requiresJdk17 {
+ *       Assume.atLeast(JdkVersion.JAVA_17);
+ *       // ... perform the actual test
+ *   }
+ * }
+ * 
+ *
+ * In addition to assumptions based on the JDK version, tests can be categorized into
+ * {@link TestGroup}s. Active groups are enabled using the 'testGroups' system property,
+ * usually activated from the gradle command line:
+ * 
+ * gradle test -PtestGroups="performance"
+ * 
+ *
+ * Groups can be specified as a comma separated list of values, or using the pseudo group
+ * 'all'. See {@link TestGroup} for a list of valid groups.
+ *
+ * @author Rob Winch
+ * @author Phillip Webb
+ * @since 3.2
+ * @see #atLeast(JavaVersion)
+ * @see #group(TestGroup)
+ */
+public abstract class Assume {
+
+
+	private static final Set GROUPS = TestGroup.parse(System.getProperty("testGroups"));
+
+
+	/**
+	 * Assume a minimum {@link JavaVersion} is running.
+	 * @param version the minimum version for the test to run
+	 */
+	public static void atLeast(JavaVersion version) {
+		if (!JavaVersion.runningVersion().isAtLeast(version)) {
+			throw new AssumptionViolatedException("Requires JDK " + version + " but running "
+					+ JavaVersion.runningVersion());
+		}
+	}
+
+	/**
+	 * Assume that a particular {@link TestGroup} has been specified.
+	 * @param group the group that must be specified.
+	 */
+	public static void group(TestGroup group) {
+		if (!GROUPS.contains(group)) {
+			throw new AssumptionViolatedException("Requires unspecified group " + group
+					+ " from " + GROUPS);
+		}
+	}
+
+	/**
+	 * Assume that the specified log is not set to Trace or Debug.
+	 * @param log the log to test
+	 */
+	public static void notLogging(Log log) {
+		assumeFalse(log.isTraceEnabled());
+		assumeFalse(log.isDebugEnabled());
+	}
+}
diff --git a/spring-build-junit/src/main/java/org/springframework/build/junit/JavaVersion.java b/spring-build-junit/src/main/java/org/springframework/build/junit/JavaVersion.java
new file mode 100644
index 00000000000..db9fee370f0
--- /dev/null
+++ b/spring-build-junit/src/main/java/org/springframework/build/junit/JavaVersion.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2012 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
+ *
+ *      http://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.build.junit;
+
+/**
+ * Enumeration of known JDK versions.
+ *
+ * @author Phillip Webb
+ * @see #runningVersion()
+ */
+public enum JavaVersion {
+
+
+	/**
+	 * Java 1.5
+	 */
+	JAVA_15("1.5", 15),
+
+	/**
+	 * Java 1.6
+	 */
+	JAVA_16("1.6", 16),
+
+	/**
+	 * Java 1.7
+	 */
+	JAVA_17("1.7", 17),
+
+	/**
+	 * Java 1.8
+	 */
+	JAVA_18("1.8", 18);
+
+
+	private static final JavaVersion runningVersion = findRunningVersion();
+
+	private static JavaVersion findRunningVersion() {
+		String version = System.getProperty("java.version");
+		for (JavaVersion candidate : values()) {
+			if (version.startsWith(candidate.version)) {
+				return candidate;
+			}
+		}
+		return JavaVersion.JAVA_15;
+	}
+
+
+	private String version;
+
+	private int value;
+
+
+	private JavaVersion(String version, int value) {
+		this.version = version;
+		this.value = value;
+	}
+
+
+	@Override
+	public String toString() {
+		return version;
+	}
+
+	/**
+	 * Determines if the specified version is the same as or greater than this version.
+	 * @param version the version to check
+	 * @return {@code true} if the specified version is at least this version
+	 */
+	public boolean isAtLeast(JavaVersion version) {
+		return this.value >= version.value;
+	}
+
+
+	/**
+	 * Returns the current running JDK version. If the current version cannot be
+	 * determined {@link #JAVA_15} will be returned.
+	 * @return the JDK version
+	 */
+	public static JavaVersion runningVersion() {
+		return runningVersion;
+	}
+}
diff --git a/spring-build-junit/src/main/java/org/springframework/build/junit/TestGroup.java b/spring-build-junit/src/main/java/org/springframework/build/junit/TestGroup.java
new file mode 100644
index 00000000000..3be8b83514b
--- /dev/null
+++ b/spring-build-junit/src/main/java/org/springframework/build/junit/TestGroup.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2012 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
+ *
+ *      http://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.build.junit;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A test group used to limit when certain tests are run.
+ *
+ * @see Assume#group(TestGroup)
+ * @author Phillip Webb
+ */
+public enum TestGroup {
+
+
+	/**
+	 * Performance related tests that may take a considerable time to run.
+	 */
+	PERFORMANCE;
+
+
+	/**
+	 * Parse the specified comma separates string of groups.
+	 * @param value the comma separated string of groups
+	 * @return a set of groups
+	 */
+	public static Set parse(String value) {
+		if (value == null || "".equals(value)) {
+			return Collections.emptySet();
+		}
+		if("ALL".equalsIgnoreCase(value)) {
+			return EnumSet.allOf(TestGroup.class);
+		}
+		Set groups = new HashSet();
+		for (String group : value.split(",")) {
+			try {
+				groups.add(valueOf(group.trim().toUpperCase()));
+			} catch (IllegalArgumentException e) {
+				throw new IllegalArgumentException("Unable to find test group '" + group.trim()
+						+ "' when parsing '" + value + "'");
+			}
+		}
+		return groups;
+	}
+}
diff --git a/spring-build-junit/src/test/java/org/springframework/build/junit/JavaVersionTest.java b/spring-build-junit/src/test/java/org/springframework/build/junit/JavaVersionTest.java
new file mode 100644
index 00000000000..1600d4ca195
--- /dev/null
+++ b/spring-build-junit/src/test/java/org/springframework/build/junit/JavaVersionTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2012 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
+ *
+ *      http://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.build.junit;
+
+import static org.hamcrest.Matchers.startsWith;
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link JavaVersion}.
+ *
+ * @author Phillip Webb
+ */
+public class JavaVersionTest {
+
+	@Test
+	public void runningVersion() {
+		assertNotNull(JavaVersion.runningVersion());
+		assertThat(System.getProperty("java.version"), startsWith(JavaVersion.runningVersion().toString()));
+	}
+
+	@Test
+	public void isAtLeast() throws Exception {
+		assertTrue(JavaVersion.JAVA_16.isAtLeast(JavaVersion.JAVA_15));
+		assertTrue(JavaVersion.JAVA_16.isAtLeast(JavaVersion.JAVA_16));
+		assertFalse(JavaVersion.JAVA_16.isAtLeast(JavaVersion.JAVA_17));
+	}
+}
diff --git a/spring-build-junit/src/test/java/org/springframework/build/junit/TestGroupTest.java b/spring-build-junit/src/test/java/org/springframework/build/junit/TestGroupTest.java
new file mode 100644
index 00000000000..2b30f299310
--- /dev/null
+++ b/spring-build-junit/src/test/java/org/springframework/build/junit/TestGroupTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2002-2012 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
+ *
+ *      http://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.build.junit;
+
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+/**
+ * Tests for {@link TestGroup}.
+ *
+ * @author Phillip Webb
+ */
+public class TestGroupTest {
+
+	@Rule
+	public ExpectedException thrown = ExpectedException.none();
+
+	@Test
+	public void parseNull() throws Exception {
+		assertThat(TestGroup.parse(null), is(Collections. emptySet()));
+	}
+
+	@Test
+	public void parseEmptyString() throws Exception {
+		assertThat(TestGroup.parse(""), is(Collections. emptySet()));
+	}
+
+	@Test
+	public void parseWithSpaces() throws Exception {
+		assertThat(TestGroup.parse("PERFORMANCE,  PERFORMANCE"),
+				is((Set) EnumSet.of(TestGroup.PERFORMANCE)));
+	}
+
+	@Test
+	public void parseInMixedCase() throws Exception {
+		assertThat(TestGroup.parse("performance,  PERFormaNCE"),
+				is((Set) EnumSet.of(TestGroup.PERFORMANCE)));
+	}
+
+	@Test
+	public void parseMissing() throws Exception {
+		thrown.expect(IllegalArgumentException.class);
+		thrown.expectMessage("Unable to find test group 'missing' when parsing 'performance, missing'");
+		TestGroup.parse("performance, missing");
+	}
+
+	@Test
+	public void parseAll() throws Exception {
+		assertThat(TestGroup.parse("all"), is((Set)EnumSet.allOf(TestGroup.class)));
+	}
+}