First off, thank you for taking the time to contribute! 👍 🎉
The Spring Framework is released under the Apache 2.0 license. If you would like to contribute something, or want to hack on the code, this document should help you get started.
- Code of Conduct
- Reporting Security Vulnerabilities
- How to Contribute
- Build from Source
- Source Code Style
- Tests
- Reference Docs
This project is governed by the Spring Code of Conduct. By participating you are expected to uphold this code. Please report unacceptable behavior to spring-code-of-conduct@spring.io.
We accept contributions created with the help of AI coding agents, but they must be carefully reviewed by a human who remains accountable for the quality of the contribution. Contributions submitted by GitHub accounts controlled by autonomous AI bots are forbidden and will result in permanent bans.
If you think you have found a security vulnerability in the Spring Framework, please DO NOT disclose it publicly until we've had a chance to fix it. Please do not report security vulnerabilities using GitHub issues or pull requests. Instead, see SECURITY.md and https://spring.io/security-policy to learn how to disclose them responsibly.
If you have a question, check Stack Overflow using this list of tags. Find an existing discussion, or start a new one if necessary.
If you believe there is an issue, search through existing issues and pull requests, trying a few different ways to find discussions, past or current, that are related to the issue. Reading those discussions helps you to learn about the issue, and helps us to make a decision.
Reporting an issue or making a feature request is a great way to contribute. Your feedback and the conversations that result from it provide a continuous flow of ideas. However, before creating a ticket, please take the time to ask and research first.
If you create an issue after a discussion on Stack Overflow, please provide a description in the issue instead of simply referring to Stack Overflow. The issue tracker is an important place of record for design discussions and should be self-sufficient.
Once you're ready, create an issue on GitHub. Please submit issues against supported versions.
Many issues are caused by subtle behavior, typos, and unintended configuration. Creating a Minimal Reproducible Example (starting with https://start.spring.io for example) of the problem helps the team quickly triage your issue and get to the core of the problem. Share it as a link to a source repository or attach it as an archive, along with instructions on how to reproduce the problem.
For enhancement requests, before explaining how you would like things to work, please describe a concrete use case for the feature and how you have tried to solve it so far.
When an issue is first created, it is flagged waiting-for-triage waiting for a team member to triage it.
Once the issue has been reviewed, the team may ask for further information if needed, and based on the findings, the issue is either assigned a target milestone or is closed with a specific status.
When a fix is ready, the issue is closed and may still be re-opened until the fix is released. After that the issue will typically no longer be reopened. In rare cases if the issue was not at all fixed, the issue may be re-opened. In most cases however any follow-up reports will need to be created as new issues with a fresh description.
-
Should you create an issue first? No, just create the pull request and use the description to provide context and motivation, as you would for an issue. If you want to start a discussion first or have already created an issue, once a pull request is created, we will close the issue as superseded by the pull request, and the discussion about the issue will continue under the pull request.
-
Always check out the
mainbranch and submit pull requests against it (for the target version see gradle.properties). Backports to prior versions will be considered on a case-by-case basis and reflected as the fix version in the issue tracker. -
Please do not submit pull requests:
- With GitHub accounts managed by autonomous AI bots.
- For issues with the label
status: waiting-for-triage, which indicates that the team has not yet triaged or decided on the issue. - For issues already assigned to someone else, since the assignee is working on it or plans to.
- For straightforward or polish-style changes.
-
Before submitting a pull request:
- Add new tests, or update existing ones, that exercise the code you have added or modified.
- Run the build and tests for the affected modules locally (see Build from Source), including the
checkstylechecks. - Carefully review the changes yourself, especially when they were generated by a coding agent.
-
Choose the granularity of your commits consciously and squash commits that represent multiple edits or corrections of the same logical change. See Rewriting History section of Pro Git for an overview of streamlining the commit history.
-
All commits must include a Signed-off-by trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin. For additional details, please refer to the blog post Hello DCO, Goodbye CLA: Simplifying Contributions to Spring.
-
Format commit messages as described in Commit Messages.
-
If there is a prior issue, reference the GitHub issue number in the description of the pull request, not in its title.
If accepted, your contribution may be heavily modified as needed prior to merging. You will likely retain author attribution for your Git commits granted that the bulk of your changes remain intact. You may also be asked to rework the submission.
If asked to make changes, simply push the changes against the same branch, and your pull request will be updated. In other words, you do not need to create a new pull request when asked to make changes.
A commit message consists of a subject line, a blank line, a description, and trailers.
- The subject line should be at most 55 characters, start with a capitalized verb in the imperative mood, and not end with a period.
It should not include prefixes like
fix:,feat:ordocs:, nor the issue or pull request number. - The description should be wrapped at 72 characters and explain the motivation for the change, for example with "Prior to this commit, ..." followed by "This commit ...".
- The description is followed by a reference to the related issue, for example
Closes gh-22276(which closes the issue once the commit is merged) orSee gh-22276(which only references it). - The last line is the
Signed-off-bytrailer, whichgit commit -sadds for you.
For example:
Fix single-value adaptation for primitive array types
Prior to this commit, a single non-array value provided for a primitive
array attribute was wrapped in a boxed array, which then failed the
compatibility check.
This commit derives the component type from the declared attribute
type so that all primitive array types accept a single value.
Closes gh-22276
Signed-off-by: Firstname Lastname <username@users.noreply.github.com>
To avoid mentioning unrelated GitHub users, annotation names such as @Override should not appear verbatim in commit messages or pull request titles.
Enclose them in backticks instead, as in `@Override`.
See the Commit Guidelines section of Pro Git for best practices around commit messages, and use git log to see some examples.
Helping to review pull requests is another great way to contribute. Your feedback can help to shape the implementation of new features. When reviewing pull requests, however, please refrain from approving or rejecting a PR unless you are a core committer for the Spring Framework.
The Spring Framework uses a Gradle build. The instructions below use the Gradle Wrapper from the root of the source tree. The wrapper script serves as a cross-platform, self-contained bootstrap mechanism for the build system.
To build you will need Git and the JDK specified in the .sdkmanrc file (currently JDK 25, for example Liberica JDK 25). The build produces artifacts compatible with Java 17+.
If you use SDKMAN!, run sdk env install to install the JDK specified in .sdkmanrc, and sdk env to use it in your current shell.
git clone git@github.com:spring-projects/spring-framework.git
cd spring-frameworkTo compile, test, and build all jars, distribution zips, and docs use:
./gradlew buildThe first time you run the build it may take a while to download Gradle and all build dependencies, as well as to run all tests.
Once you've bootstrapped a Gradle distribution and downloaded dependencies, those are cached in your $HOME/.gradle directory.
Gradle has good incremental build support, so run without clean to keep things snappy.
You can also use the :<project> prefix to only run the tasks of a given module and the modules it depends on.
For example, if iterating over changes in spring-webmvc, run the following to build and test that module:
./gradlew :spring-webmvc:testTo run a single test class or method, use the --tests option:
./gradlew :spring-core:test --tests org.springframework.util.StringUtilsTestsTo run all checks for a module, including tests and checkstyle, use:
./gradlew :spring-core:checkIf you need to publish Spring Framework artifacts locally for testing, you can do the following:
./gradlew pTML -PskipDocspTML is an abbreviation for the publishToMavenLocal task.
The skipDocs property will skip the "documentation" and "distribution" tasks (typically, the javadoc, kdoc and zip artifacts for docs in general).
This can be useful for local iterations, but it is advised to run the full build before submitting a pull request.
To install all Spring Framework jars in your local Maven repository, use the following:
./gradlew publishToMavenLocalEnsure that the JDK specified in .sdkmanrc is configured in your IDE.
Then follow the instructions for Eclipse or IntelliJ IDEA.
This section defines the coding standards for source files in the Spring Framework. Its structure is based on the Google Java Style reference. Many of these rules are enforced at build time by Checkstyle.
Above all, please carefully follow the whitespace and formatting conventions already present in the code you are modifying, and do not reformat code for its own sake.
- Source files must be encoded using
UTF-8. - Indentation uses tabs, not spaces.
- Use Unix (LF), not DOS (CRLF), line endings.
- Eliminate all trailing whitespace.
A source file consists of the following, in this exact order:
- License
- Package statement
- Import statements
- Exactly one top-level class
Exactly one blank line separates each of the above sections.
Each source file must specify the following license at the very top of the file:
/*
* 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.
*/The import statements are structured as follows:
- import
java.* - blank line
- import
javax.* - import
jakarta.* - blank line
- import all other imports
- blank line
- import
org.springframework.* - blank line
- import static all other imports
Static imports should not be used in production code, but they should be used in test code, especially for things like import static org.assertj.core.api.Assertions.assertThat;.
Although static imports are generally forbidden in production code, the following are use cases for which static imports are permissible:
- constants (including enum constants): such as those in
java.nio.charset.StandardCharsetsororg.springframework.core.annotation.MergedAnnotations.SearchStrategy - static factory methods for third-party DSLs: such as the methods in
org.junit.platform.engine.discovery.DiscoverySelectorswhen used with the JUnit PlatformLauncherAPI
Wildcard imports such as import java.util.* or import static org.assertj.core.api.Assertions.* are forbidden, even in test code.
The following governs how the elements of a source file are organized:
- static fields
- normal fields
- constructors
- (private) methods called from constructors
- static factory methods
- JavaBean properties (i.e., getters and setters)
- method implementations coming from interfaces
- private or protected templates that get called from method implementations coming from interfaces
- other methods
equals,hashCode, andtoString
Note that private or protected methods called from method implementations should be placed immediately below the methods where they're used. In other words, if there are 3 interface method implementations with 3 private methods (one used from each), then the order of methods should include 1 interface and 1 private method in sequence, not 3 interface and then 3 private methods at the bottom.
Above all, the organization of the code should feel natural.
Braces mostly follow the Kernighan and Ritchie style (a.k.a., "Egyptian brackets") for nonempty blocks and block-like constructs:
- No line break before the opening brace but prefixed by a single space
- Line break after the opening brace
- Line break before the closing brace
- Line break after the closing brace if that brace terminates a statement or the body of a method, constructor, or named class
- Line break before
else,catch, andfinallystatements
Example:
return new MyClass() {
@Override
public void method() {
if (condition()) {
something();
}
else {
try {
alternative();
}
catch (ProblemException ex) {
recover();
}
}
}
};90 characters is the preferred line length we aim for. In some cases the preferred length can be achieved by refactoring code slightly. In other cases it's just not possible.
90 is not a hard limit. Lines between 90-105 are perfectly acceptable in many cases where it aids readability and where wrapping has the opposite effect of reducing readability. This is a judgement call and it's also important to seek consistency. Many times you can learn by looking at how specific situations are handled in other parts of the code.
Lines between 105-120 are allowed but discouraged and should be few.
No lines should exceed 120 characters.
The one big exception to the above line wrapping rules is Javadoc where we aim to wrap around 80 characters for maximum readability in all kinds of contexts, for example, reading on GitHub, on your phone, etc.
When wrapping a lengthy expression, 90 characters is the length at which we aim to wrap.
Place separator symbols at the end of the current line rather than at the beginning of the next line.
In this context, the following are considered separator symbols: ,, +, ?, :, &&, ||.
For example:
if (thisLengthyMethodCall(param1, param2) && anotherCheck() &&
yetAnotherCheck()) {
// ....
}Add two blank lines before the following elements:
static {}block- Fields
- Constructors
- Inner classes
Add one blank line after a method signature that is multiline, i.e.
@Override
protected Object invoke(FooBarOperationContext context,
AnotherSuperLongName name) {
// code here
}For inner classes, extra blank lines around fields and constructors are typically not added as the inner class is already separated by 2 lines, unless the inner class is more substantial in which case the 2 extra lines could still help with readability.
The src/idea/spring-framework.xml file provides an IntelliJ IDEA code style scheme with the settings we use, and ./gradlew cleanEclipse eclipse applies the Eclipse settings we use.
Notable differences from the IntelliJ IDEA defaults are:
- Use tab character for indentation
- Add a space before the left brace of an array initializer
- Keep when reformatting: multiple expressions in one line, simple blocks in one line
else,catch, andfinallyon new line- Method declaration parameters: do not align when multiline
- Javadoc formatting disabled
- High thresholds for "Class count to use import with
*" and "Names count to use static imports with*" so that imports are always listed individually - Import layout as described in Import Statements
Try as much as possible to put the implements, extends section of a class declaration on the same line as the class itself.
Order the classes so that the most important comes first.
Constant names use CONSTANT_CASE: all uppercase letters, with words separated by underscores.
Every constant is a static final field, but not all static final fields are constants.
Constant case should therefore be chosen only if the field is really a constant.
Example:
// Constants
private static final Object NULL_HOLDER = new NullHolder();
public static final int DEFAULT_PORT = -1;
// Not constants
private static final ThreadLocal<Executor> executorHolder = new ThreadLocal<>();
private static final Set<String> internalAnnotationAttributes = new HashSet<>();Avoid using single characters as variable names.
For instance, prefer Method method to Method m.
- A file should look like it was crafted by a single author, not like a history of changes.
- Don't artificially spread things out that belong together.
Choose wisely where to add a new setter method; it should not be simply added at the end of the list. Perhaps the setter is related to another setter or relates to a group. In that case it should be placed near related methods.
- Setter order should reflect order of importance, not historical order.
- Ordering of fields and setters should be consistent.
Wrap the ternary operator within parentheses, for example, return (foo != null ? foo : "default");.
Also make sure that the not null condition comes first.
Use the org.springframework.util.Assert.notNull static method to check that a method argument is not null and throw an IllegalArgumentException otherwise.
Format the exception message so that the name of the parameter comes first with its first character capitalized, followed by "must not be null".
For instance:
public void handle(Event event) {
Assert.notNull(event, "Event must not be null");
//...
}For other use cases, use the org.springframework.util.Assert.state static method to ensure a variable is not null and throw an IllegalStateException otherwise.
Format the exception message so that the identifier related to the variable (name, type or description) comes first with its first character capitalized, followed by "must not be null".
For instance:
//...
Event event = ...
Assert.state(event != null, "Event must not be null");
//...The null-safety of Spring Framework APIs and fields must be specified using JSpecify annotations, and consistency is enforced at build time via NullAway. For details, see the Null-safety chapter of the reference manual.
Packages are expected to define non-null as the default in package-info.java:
@NullMarked
package org.springframework.mypackage;
import org.jspecify.annotations.NullMarked;Nullable fields, return values, and parameters are expected to be specified explicitly using @Nullable:
import org.jspecify.annotations.Nullable;
public class MyClass {
private @Nullable Class<?> nullableType;
public void setNullableType(@Nullable Class<?> nullableType) {
this.nullableType = nullableType;
}
public @Nullable Class<?> getNullableType() {
return this.nullableType;
}
}Spring's @Contract annotation can be used to specify some aspects of the method behavior depending on the arguments that are then taken into account by NullAway.
See for example how @Contract is used within the Spring Framework codebase in the Assert class:
public abstract class Assert {
// ...
@Contract("false, _ -> fail")
public static void state(boolean expression, String message) {
if (!expression) {
throw new IllegalStateException(message);
}
}
// ...
@Contract("null, _ -> fail")
public static void notNull(@Nullable Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}
// ...
}Those contracts allow NullAway's static analysis to understand that after an invocation of Assert.state(value != null, "Value must not be null") or Assert.notNull(value, "Value must not be null"), a nullable value can be considered as non-nullable.
Related guidelines:
- When overriding a method, null-safety annotations of the super method need to be specified on the overridden method unless you want to override null-safety.
- Use
@SuppressWarnings("NullAway")when NullAway triggers irrelevant errors, which can happen due to NullAway bugs or missing features or when the analysis is not able to prove null-safety (for example, with lambdas). - Only use the
@Nullableand@NonNullannotations from theorg.jspecify.annotationspackage.
Always add @Override on methods overriding or implementing a method declared in a super type.
@sinceshould be added to every new class with the version of the framework in which it was introduced.@sinceshould be added to any new public and protected methods of an existing class.@sinceomits a.0patch version (for example,@since 7.1rather than@since 7.1.0), but includes it otherwise (for example,@since 7.0.3for an addition in a maintenance release).
A class that is only a collection of static utility methods must be named with a Utils suffix, must have a private default constructor, and must be abstract.
Making the class abstract and providing a private default constructor prevent anyone from instantiating it.
For example:
public abstract class MyUtils {
private MyUtils() {
/* prevent instantiation */
}
// static utility methods
}A field of a class should always be referenced using this.
A method of a class, however, should never be referenced using this.
The use of var for variable declarations (local variable type inference) is not permitted.
Instead, declare variables using the concrete type or interface (where applicable).
Note, however, that var is permitted within test methods if used consistently within the test and preferably within the entire test class.
Printing to System.out or System.err, and calling printStackTrace(), are forbidden in the main codebase.
The following template summarizes typical Javadoc usage for a method.
/**
* Parse the specified {@link Element} and register the resulting
* {@link BeanDefinition BeanDefinition(s)}.
* <p>Implementations must return the primary {@link BeanDefinition} that results
* from the parsing if they will ever be used in a nested fashion (for example as
* an inner tag in a {@code <property/>} tag). Implementations may return
* {@code null} if they will <strong>not</strong> be used in a nested fashion.
* @param element the element that is to be parsed into one or more {@link BeanDefinition BeanDefinitions}
* @param parserContext the object encapsulating the current state of the parsing process;
* provides access to a {@link org.springframework.beans.factory.support.BeanDefinitionRegistry}
* @return the primary {@link BeanDefinition}
*/
BeanDefinition parse(Element element, ParserContext parserContext);In particular, please note:
- Use an imperative style (i.e. Return and not Returns) for the first sentence.
- No blank lines between the description and the parameter descriptions.
- If the description is defined with multiple paragraphs, start each of them with
<p>. - If a parameter description needs to be wrapped, do not indent subsequent lines (see
parserContext).
The Javadoc of a class has some extra rules that are illustrated by the sample below:
/**
* Interface used by the {@link DefaultBeanDefinitionDocumentReader} to handle custom,
* top-level (directly under {@code <beans/>}) tags.
*
* <p>Implementations are free to turn the metadata in the custom tag into as many
* {@link BeanDefinition BeanDefinitions} as required.
*
* <p>The parser locates a {@link BeanDefinitionParser} from the associated
* {@link NamespaceHandler} for the namespace in which the custom tag resides.
*
* @author Rob Harrop
* @since 2.0
* @see NamespaceHandler
* @see AbstractBeanDefinitionParser
*/- Each class must have a
@sincetag with the version in which the class was introduced. - The order of tags for type-level Javadoc is:
@author,@since,@param,@see,@deprecated. - The order of tags for constructor-level, method-level, and field-level Javadoc is:
@param,@return,@throws,@since,@see,@deprecated. - In contrast to constructor-level, method-level, and field-level Javadoc, the paragraphs of a class description are separated by blank lines.
The following are additional general rules to apply when writing Javadoc:
- Use
{@code}to wrap code statements or values such asnull. - If a type is only referenced by a
{@link}element, use the fully qualified name in order to avoid an unnecessaryimportdeclaration.
Any code change should come with new or updated tests.
- Tests must be written using JUnit Jupiter.
The only exceptions to this rule are test classes in the
spring-testmodule that specifically test Spring's integration with JUnit 4 and TestNG. - Each test class name must end with a
Testssuffix. - Use AssertJ for assertions and assumptions, including specialized assertions such as
assertThatIllegalArgumentException()instead ofassertThatExceptionOfType(IllegalArgumentException.class). - Use Mockito for mocks and spies.
The reference documentation is authored in AsciiDoc format using Antora.
The source files for the documentation reside in the framework-docs/modules/ROOT directory.
Java and Kotlin code snippets included via include-code:: reside in framework-docs/src/main and are compiled as part of the build.
For trivial changes, you may be able to browse, edit source files, and submit directly from GitHub.
When making changes locally, execute ./gradlew antora and then browse the results under framework-docs/build/site/index.html.
Asciidoctor also supports live editing. For more details see AsciiDoc Tooling.