-
Notifications
You must be signed in to change notification settings - Fork 699
Spring Data 2025.1 Release Notes
-
Upgrade to Spring Framework 7
-
Upgrade to Jakarta EE 11 (JPA 3.2, Servlet 6.1)
-
Upgrade to Kotlin 2.2
-
Upgrade to Jackson 3
-
Ahead of Time Repositories
-
Strong use of JPQL in JPA
-
Removal of
ListenableFuturesupport
Details
-
Spring Data Build - 4.0
Spring Data JPA, JDBC, MongoDB, and Cassandra now ship with AOT repository support generating code and metadata for repository query methods. AOT repositories are enabled by default and can be either disabled entirely (by setting the spring.aot.repositories.enabled=false property) or individually by store module (by setting the spring.aot.jpa.repositories.enabled=false/spring.aot.mongodb.repositories.enabled=false/spring.aot.[module-identifier].repositories.enabled=false property).
AOT generated repository fragments are used when running the application in AOT mode and allow debugging of the actual query method. Generated methods do not require reflective implementation of the query methods by moving the implementation workload to the build phase.
Spring Data generates and loads classes since its late 1.x versions for property accessors and entity instantiators when possible. Representing property access and entity instantiation as bytecode avoids reflective overhead as much as possible, though field access requires the use of MethodHandle.
Generated classes aren’t ideal though, as they have to be generated during runtime and injected into the class loader using the same protection domain. When running as Graal Native image, class declaration is not possible at all.
During AOT Repository explorations, we decided to include generated property accessors and entity instantiators when introspecting managed types and repository interfaces. When Spring Data finds a generated property accessor/entity instantiator, it does not need to generate one. Additionally, the generated class resides in the same origin as your domain classes being on the same class path. With that, we can use these generated classes in Graal Native images. Regardless of running in AOT JVM mode or running your application as Graal Native image, pre-generated accessors reduce initial memory consumption and CPU pressure.
Spring Data has upgraded in accordance with Spring Framework 7.0 to Jackson 3.
Jackson 3 uses a different package (tools.jackson) for its core implementation, however, the annotation artifact resides in the old namespace (com.fasterxml.jackson) and must be upgraded to a 3.x version.
Spring Data’s Web support now ships with a ProjectingJacksonHttpMessageConverter, GeoJacksonModule, and SpringDataJackson3Configuration using Jackson 3. The configuration SpringDataWebConfiguration auto-detects presence of Jackson 3 and registers components accordingly using Jackson 2 as fallback. In case both Jackson versions are available, Jackson 3 is preferred.
SpringDataJacksonConfiguration, SpringDataJacksonModules, GeoModule, and ProjectingJackson2HttpMessageConverter are deprecated for removal. These classes will be removed once Spring Framework removes its Jackson 2 support.
With Spring Framework removing support for ListenableFuture, we’re removing support for ListenableFuture as well. Please use CompletableFuture when using @Async query methods.
PersistenceConstructor Migration
-
Removal of
@PersistenceConstructorin favor of@PersistenceCreator -
Removal of
Parameter.hasSpelExpression()in favor ofhasValueExpression(). -
Removal of
PersistentEntity.getPersistenceConstructor()in favor ofPersistentEntity.getInstanceCreatorMetadata()andisConstructorArgument(…)in favor ofisCreatorArgument(…). -
Removal of
PreferredConstructor.isConstructorParameter(…)isCreatorParameter(…). -
Removal of
MappingInstantiationException.getConstructor(…)in favor ofgetEntityCreator().
SpEL to Value Expression Migration
-
Removal of
DefaultSpELExpressionEvaluatorandSpELExpressionEvaluatorin favor ofValueExpressionEvaluator. -
Removal of
SpELExpressionParameterValueProviderin favor ofValueExpressionParameterValueProvider. -
Removal of
ValueExpressionParameterValueProvider.potentiallyConvertSpelValue(…)in favor ofpotentiallyConvertExpressionValue(…). -
Removed
ExtensionAwareQueryMethodEvaluationContextProvider,QueryMethodEvaluationContextProviderand their reactive variants in favor of Value Expression support.QueryMethodValueEvaluationContextAccessor.createEvaluationContextProvider(beanFactory)can help to create default instances. -
Removed
SpelEvaluatorandSpelQueryContextin favor ofValueExpressionQueryRewriter
QueryMethod Parameters Revision
-
Removed
QueryMethod.createParameters(Method method, TypeInformation<?> domainType)in favor ofcreateParameters(ParametersSource parametersSource) -
Removal of
Parameter(MethodParameter parameter)andParameters(Method method, Function<MethodParameter, T> parameterFactory).
ClassUtils and ReflectionUtils Revision
-
Removal of
CastUtilswithout replacement. Apply casting where necessary. -
Removal of
org.springframework.data.repository.util.ClassUtils. Several methods went intoorg.springframework.data.util.ClassUtilsandReflectionUtils.
As described in #3298, we are stopping support for proxying web handler method parameters that are not explicitly annotated with @ProjectedPayload (either at the type or method param level). This release begins the removal process by logging a warning for each qualifying web handler method parameter that this style is deprecated and that we will drop support for this in the next minor release.
Our current plan is to stop supporting these non-annotated web handler method parameters but continue logging their occurrence in 4.1, and remove the logging altogether in 4.2.
Other Changes
-
Removal of
PersistentPropertyPath.getRequiredLeafProperty()in favor ofgetLeafProperty(). -
QPageRequestconstructors areprivatenow, useoffactory methods. -
Removal of
AnnotationRepositoryConfigurationSourceconstructor -
Removal of
RepositoryFactorySupport.getTargetRepositoryViaReflection(…), useinstantiateClass(…)instead. -
Removed
PagedResourcesAssembler.getMethodParameter(…)without replacement -
Removed
org.springframework.data.repository.util.ReactiveWrappersin favor of the variant in theorg.springframework.data.utilpackage. -
ClassTypeInformationwas made package-protected. Obtain instances ofTypeInformationusing the appropriate factory methods onTypeInformation. -
Removal of the
org.springframework.data.type.classreadingpackage in favor of Spring’sAnnotationMetadata.
Spring Data JPA 4.0 upgrades its baseline to JPA 3.2, requiring Hibernate 7.1 and respective Eclipselink 5.0 runtime versions. With the upgrade, we refined our implementations to use Query.getSingleResultOrNull() for single-result query methods, resulting in the avoidance of NoResultException. Further changes include support of union, intersect, except, cast, left, right, and replace functions, and support for the || string concatenation operator.
With that, we also support Hibernate’s JSON and XML Set-returning functions and JSON and XML functions like json_object() or xmlquery().
Upgrading to JPA 3.2 allows consistent support for nulls precedence in Sort expressions as the Criteria Query API allows declaration of nulls precedence. Previously, we only supported nulls precedence in JPQL queries.
15 years ago, Spring Data JPA started to see first light. Back then, it used String-based queries and eventually moved to JPA’s CriteriaQuery API to avoid string concatenation when building queries. It has been quite a ride with the Criteria API being powerful yet restricted as Hibernate was capable of way more functionality than exposed through the Criteria API (e.g. Nulls Precedence when sorting).
A couple of years into Criteria API, we started noticing a huge performance penalty using Criteria API. JPA providers have to evaluate the entire query on each query creation, whereas Hibernate is able to cache queries much better when using String-based JPQL queries.
We finally decided to explore a JPQL-based approach to derived queries and we found a 3.5x improvement in running such queries. While the factor 3.5x applies to in-memory databases, a typical application can still benefit from a roughly 25% improvement in query throughput by leveraging Hibernate’s query caching.
Using a different API that has proven over 15 years bears quite some risk in breaking applications that otherwise ran fine. We would appreciate your feedback on cases that behave differently than the previous version.
We revised our Specification API to reflect its usage better. Over time, we introduced specification support for delete queries, which uses a different query type hierarchy (CriteriaDelete), and that wasn’t an ideal fit for existing interfaces accepting CriteriaQuery.
We introduced DeleteSpecification and UpdateSpecification to enable seamless usage of delete respective update queries. Along with that change, we also introduced PredicateSpecification to define reusable specification functions that return Predicate regardless of the query context in which they are used.
Related to a refined Specification API, we extended the fluent findBy(…) API to return a Slice without running a count query. Also, paginated queries optionally accept a count specification if the count query should be provided (i.e., an optimized query).
A selected set of expressions can now be used with JpaSort.unsafe(…) together with Specifications. The newly introduced parser for ORDER BY expressions translates ORDER BY items to JPA’s Criteria API Expressions as much as possible. ORDER BY parsing can translate simple path expressions, function calls, and CASE clauses. Any more complex ordering items should be provided directly to CriteriaQuery.
Spring’s LocalContainerEntityManagerFactoryBean respective LocalEntityManagerFactoryBean create a shared EntityManager instance rendering our own SharedEntityManagerCreator registration obsolete. We now no longer register a shared EntityManager if the EntityManagerFactory bean is provided through AbstractEntityManagerFactoryBean. Additionally, we deprecated EntityManagerBeanDefinitionRegistrarPostProcessor and we do not register EntityManagerBeanDefinitionRegistrarPostProcessor that registers autowiring-capable EntityManager beans.
If you should require a shared EntityManager as per our previous setup, you can register EntityManagerBeanDefinitionRegistrarPostProcessor yourself. Addiitionally, you can provide a BiPredicate<String, BeanDefinition> to determine for which named bean definitions to register a SharedEntityManagerCreator.
With this release, we widely revised DeclaredQuery and QueryEnhancer. Since introducing JSqlParser with Spring Data 3.0, we have constantly extended capabilities to introspect and rewrite queries. At some point, we were asked to force JSqlParser (or disable JSqlParser usage) but that turned out to be impossible given its design. We had to revise our arrangement entirely to decouple the individual parsing and analysis stages a declared query goes through.
Additionally, parts of DeclaredQuery and QueryEnhancer have been public API without intending to do so. With this major revision, we decoupled DeclaredQuery from its sole StringQuery implementation that would parse and introspect queries without a way to provide a strategy to select the intended QueryEnhancer.
QueryEnhancer is now slimmed down to essential methods that are necessary for Query Introspection and Rewriting. QueryEnhancerSelector allows the implementation of QueryEnhancer selection (Regex, JSqlParser, JPQL). You can even provide your own QueryEnhancer implementation.
With that change, the spring.data.jpa.query.native.parser option is gone and no longer available. You can configure QueryEnhancerSelector through @EnableJpaRepositories:
@Configuration
@EnableJpaRepositories(queryEnhancerSelector = MyQueryEnhancerSelector.class)
class ApplicationConfig {
// …
}DbActionExecutionException gets removed without replacement. Exceptions from NamedParameterJdbcTemplate now get thrown directly, without the extra wrapper.
In an aggregate root one may now use an entity as id.
Just annotating the field with @Id is sufficient.
It will be treated as an @Embedded. For new entities values for the ids have to be created explicitly in an BeforeConvertCallback or the entity has the be inserted explicitly using JdbcAggregateTemplate.insert
Up to now, with Spring Data R2DBC identifiers (table and column names) were by default used unquoted. This causes problems when the name happens to be a key word or contains special characters.
We changed this default to match the behavior of Spring Data JDBC: All identifiers are now quoted. Keywords and such are no longer a problem.
But this comes at a cost.
Most databases convert unquoted identifiers to upper case (a few to lower case) making them effectively case insensitive.
Names generated by Spring Data are not affected since we use the letter casing preferred by the database in use.
But if you specify identifiers in @Column or @Table annotations you must take care that the letter casing does match.
If you want the old behavior back you can call setForceQuote(false) on the RelationalMappingContext which R2dbcMappingContext extends.
Spring Data R2DBC supports composite ids as well. It behaves just as the JDBC version (see above).
Spring Data MongoDB has updated its default format representation to Decimal128 for BigDecimal values in alignment with the MongoDB driver. We also have removed the default for BigInteger and require an explicit configuration to avoid unintended representation changes after the version upgrade.
Previously, we’ve rendered BigInteger and BigDecimal as String. While a string-based representation retains precision it is not possible to sort such values properly or even run range queries. This is a subtle change because it doesn’t show up as compile error. Existing data must be either migrated or the default can be switched when configuring MongoCustomConversions:
MongoCustomConversions.create(config -> config.bigDecimal(BigDecimalRepresentation.DECIMAL128)); // recommended for numeric sorting and interop
MongoCustomConversions.create(config -> config.bigDecimal(BigDecimalRepresentation.STRING)); // previous versions before Spring Data MongoDB 5.0Very large values, though being a valid BigInteger or BigDecimal, might exceed the maximum bit length of org.bson.types.Decimal128 in their store native representation.
Note that you can control the BigDecimal (and BigInteger) representation through the spring.data.mongodb.representation.big-decimal property (DataMongoProperties.Representation.bigDecimal) when using Spring Boot.
|
Warning
|
Changes to a default where persistent values use a different format will result in a different behavior unless all data is migrated and it is a change that isn’t obvious. |
In previous versions, Spring Data MongoDB used the JAVA_LEGACY (BSON binary subtype 3) UUID representation by default. Similar to the Big Number changes, we have removed defaulting in alignment with the driver.
Note that you can control the UUID representation through the spring.data.mongodb.uuid-representation property (MongoProperties.uuidRepresentation) when using Spring Boot.
|
Warning
|
Changes to a default where persistent values use a different format will result in a different behavior unless all data is migrated and it is a change that isn’t obvious. |
JMX support has been deprecated in version 4.5 and removed in 5.0. We recommend switching to Spring Boot Actuator Endpoints and exposing those over JMX if needed.
We changed the @Meta.allowDiskUse annotation value from boolean to String to provide a way of applying the server-default. Previously, a boolean value would always be associated with a value that hides the change of the server default. The underlying DiskUsage enumeration allows using either the default (when unset), allowing or denying disk usage.
The newly introduced QueryResultConverter allows to contextually post-process results, providing access to both the raw Document as well as the already mapped object, which can be useful when there is a need to apply additional transformations.
List<Optional<Jedi>> result = template.query(Person.class)
.as(Jedi.class)
.matching(query(where("firstname").is("luke")))
.map((document, reader) -> Optional.of(reader.get()))
.all();With Spring Framework removing support for ListenableFuture, we’re removing support for ListenableFuture as well. Packages org.springframework.data.cassandra.core.cql.legacy and o.s.d.c.core.legacy are removed. Use asynchronous template API implementations and their utilities from o.s.d.c.core.cql respective o.s.d.c.core returning CompletableFuture.
Other removals:
-
Removal of the
forceQuoteattributes in the mapping configuration -
Removal of the
MappingContext-relatedUserTypeResolver(useMappingCassandraConverterto configure theUserTypeResolver) -
Removal of Kotlin extensions accepting
KClass. Use the reified generics variant instead. -
Removal of
TupleTypeFactoryalong with their implementations. -
Removal of various FactoryBeans that no longer match the Cassandra Driver 4 setup and were previously used to configure independent
ClusterandSessionobjects. -
Removal of the Prepared Statement Caches, the driver caches prepared statements on its own.
-
Removal of deprecated methods.
The new QueryResultConverter is useful when there is a need to apply additional contextual transformations as it provides access to both the Row as well as the already mapped object as outlined below.
List<Optional<Jedi>> results = template.query(Person.class)
.as(Jedi.class)
.map((row, reader) -> Optional.of(reader.get())
.all();We added support for Jackson 3 through Jackson3JsonRedisSerializer and Jackson3HashMapper. Jackson 3 uses slightly different defaults (e.g. stricter type identifier checking in the context of default typing, string-representation for Calendar values) requiring to use compatibility settings when using Jackson3HashMapper:
Jackson3HashMapper.create(Jackson3HashMapper.Jackson3HashMapperBuilder::jackson2CompatibilityMode);Jackson 2 serializers are now deprecated.
We’ve removed MicrometerTracingAdapter in favor of Lettuce’s built-in Micrometer support through MicrometerTracing.
Spring Data Redis is now annotated using JSpecify annotations. Command and Operations interfaces are deliberately @NullUnmarked as Redis' Transactional behavior renders each method conditionally nullable based on whether it is used in the context of transactions or pipelining. We consider this to be the best compromise between nullness indication and the trouble that stems from working around nullness indication that isn’t applicable in a certain context.
-
M1 - January 2025
-
M2 - April 2025
-
M3 - May 2025
-
M4 - July 2025
-
RC1 - Sept 2025
-
RC2 - Oct 2025
-
GA - Nov 2025
-
OSS Support until: May 2025
-
End of Life: Sept 2026