About this talk
In this talk, Mark Paluch, the Spring Data project lead, discusses the evolution of repository query methods and the need for enhancing their usability in complex applications. He emphasizes the challenges developers face with string-based properties in queries, which can lead to issues during refactoring and code maintenance. Paluch introduces typed property paths as a solution, allowing developers to express queries with more robustness and contextual information, improving IDE support and error handling. He also mentions integrations with tools like QueryDSL and JPA and highlights the benefits of using functional programming techniques to make queries more expressive. By focusing on Java and Kotlin support, the talk aims to inspire developers to explore new ways of implementing queries that restore joy to the coding experience.
Full transcript
[music] >> Most of us started small. Maybe somewhere here. Spring has us covered for our everyday tasks. It makes our everyday lives easier with repository query methods. And supposedly we are all here because we think we do enjoy queries. Until we don't. Repository query methods have evolved over time as our understanding of applications and the domain evolves and changes. And with that our applications become more complex.
Our query become more demanding. And eventually what started as a method name turns into documentation. And sometimes into a cry for help. I am Mark Paluch. Spring Data project lead. And today I am going to talk how to bring again joy into queries. At some point are just not the right tool for the job. We will have to change to something different, more flexible. Giving us more
convenience. Better ways to express what we want to achieve. Instead of causing humongously long method names. I know Spring is pretty much known for very long method names, but that doesn't mean that you have to suffer for these from inside of your code and your application. With the query API you typically declare what you want to query, the property names, the criteria, the way how you want
to run your query. And in the context you typically run those queries with within within some sort of template API. All this good until this point, but you still feel something is off. Something just doesn't feel right. As our applications evolve we change things around, move things around. Our understanding of the domain evolves, improves. And we reflect that naturally in our code. We start refactoring. We start
changing and applying those changes to our domain model. All is good. Everything is compiling. And your production becomes essentially your test suite. We haven't probably noticed that something broke. And that is where string based properties start to fall apart. We express we are used to express our queries or property references as text. They become strings, right? Meaningless text without any additional context. For us as a developer
who is aware of oh, I'm going to use that query in the context of a user object. First name, last name makes sense. But for anything else such as the compiler or your IDE probably they don't care too much. They might care if they have proper integrations. But for the majority of cases you change something and your only option to catch up with those changes is go
across your code base, do search and replace. Maybe an AI agent will help you with those. But for the majority of time that I experienced with working stringly based stringly type programming things fell pretty quickly apart. Essentially all the contextual information is missing that we see from looking at a data access a a code snippet. But it is not being captured within the bits and parts that
we try to express. Now you might say the community has solved that problem multiple times. And you're right. If you're using JPA there's the JPA meta model. for me to get to know you better, who of you is is using JPA? Quick raise of hands. Huh? Quite a couple. So what what do all the other people do? MongoDB? Redis? Okay. And and you know with every single
talk there is this one single LDAP person that is using Spring Data LDAP. So like like the Emacs person in Java talks. Is is anybody is this LDAP person here today? One. Again. Yeah. Cool. Thank you. Um So in JPA you have the possibility to leverage the JPA meta model. Essentially an annotation processor that goes over your annotated or mapped domain model and generates classes that reflect
what is being defined in the domain model itself. It gives you a level of safety once you change something within your domain Upon the next compilation run your code will likely not compile anymore. And that will give you an indication that you need to catch up. You can do things also the other way around that you rename a property in your domain model. And immediately do a
refactoring on the generated code to save you a couple of cycles. But honestly for me it's been in the past that I've changed something. I've hit compile and then I walked myself through all the compiler errors. That's taking away from the joy of queries. It's essentially a lot of pain really and burning a lot of cycles. If you're not a JPA user there needs to be something
else. And there is in fact. There's QueryDSL. QueryDSL has evolved quite a long time. And it is it combines a meta model with a way how to express criteria predicates, sorting, and many many more things. Spring Data integrates at certain points with QueryDSL. Allowing you to leverage the QueryDSL model to express queries with quite some flexibility really. However, it still has this aspect that you have to
go and touch your build. You have to use annotation processing. And these days integrating with annotation processors has become more elaborate and more complex depending on your build tooling. So you start realizing there is already a toll on on these things. Taking inspiration from a different field, slightly different. As a juke user model based a model based approach is the way to go really. You either generate
your model entirely from the database and you're able to do so on every single build. Or you maintain that model manually yourself. In highly regulated environments though you have difficulties to get even through the firewall to towards the database. So this might be not always an option. Juke is more SQL centric around all of the elements that you find within the database, the table, the column. Right?
So so let's domain centric. And Spring Data tries to be domain centric. And approaches queries from a domain driven design approach. And if you are not using any relational sorry, then Juke is probably not going to work for you for your particular project. There's out there Cassandra, MongoDB. A lot of a lot of other other databases. And that brings us basically back to where we started. To
our query API. That is brittle. That is error prone. though it gives us a lot of flexibility we feel we need to better do better. The goal is not to change everything. It's all the fundamental assumptions that that we've made into our code. But rather give it a bit more robustness. Give it a bit more expressivity, and use something that addresses a lot of those concerns that
we see in other areas that are probably the reasons that we don't use JPA meta models all of the time, that we don't use QueryDSL. And we can do and the change is rather subtle. How about using something that is already built into the language that we are familiar with overly pollute our code though it's a bit more to read but on the you get something that
gives you safety and and confidence confidence. Something that integrates well with an IDE. And the way we strongly typed properties is by using method references, a functional style to express these This idea isn't isn't new. There's prior art already. There are examples in MyBatis. Kotlin has a way to directly address properties with its KProperty syntax. We had such experiments already in that allowed us to construct property
paths by just writing a path towards the Kotlin KProperty helping with readability with almost zero cost because everything you need is already baked in both in the language of Kotlin and through those extensions that Spring Data provides and and you as a developer can take advantage of these constructs to make your code much more expressible expressive. Now Now typed property paths >> [clears throat] >> require a
domain model. Therefore, it is a great fit for Spring Data because you have domain models already, especially if you work with repositories. You have a domain type. You have properties. You have a representation of whatever you are working with. The goal has been to be Java native, no annotation processors, no plugins, just pure Java. if you are a Spring Data user already this feature is only one
upgrade away from you. It's important to us when we design such features to pay a lot of attention how something like that can be used and integrated within your everyday code. We do not want to clutter your code with things that create a lot of noise or distract you from the actual thing that you are trying to do. Remember this those old old good days of reactive
programming where essentially your business application code was all monos and fluxes. That is the the strong opposite of the goal that we try to achieve here. Rather, we try to be as as minimalistic as possible but yet as expressive as possible. We do not want you to take a route through difficult to use static APIs or builders but rather allow you to declare those references at those
areas you are using to express your your query already. the functional style is a perfect match for expressing such such information. Though method references have their origins and lambda expressions in a more declare functionality and declare behavior and not so much to provide structural information to to elements. However, Java does not have a native way to express properties through language but it has still its Java Beans
specification, at least the idea of that. And that is really what we are following here. Your domain model already makes use of Java Bean properties and so these two things are pretty great match. API that is accepting strings isn't going away. Quite the contrary. We have been retrofitting all of our API where it makes sense, where we accept properties, to additionally accept type property path allowing you
to gradually opt in into that model, explore whether it brings you benefit and enable you to step by step adopt that model if you think it brings you more benefit than than it costs costs you. And really the granularity >> is a single property, is a sort statement that that is kind of your entry point that you can really start small and immediately take advantage from more
expressivity and more contextual deep of typed property paths. You can go even a level deeper. Your domain model already does. You have all the structure already within your domain model. Clearly, there is no operator overloading yet in Java. There is however operator overloading in Kotlin making navigation across properties very convenient and also with extension model that Kotlin provides where you can mangle various generics you get a
lot of flexibility that enables Kotlin to be much more flexible when expressing property paths and composing those into deeply nested On the Java side, however the API looks rather involved and I think we haven't found the sweet spot there yet. Still, this is an evolution. For single segment property paths, you get top-notch experience. For anything that requires composition, that requires unwrapping entry points via property path are
currently the the way to go to explore and to see where does this take us and how can we improve from from here benefits involved with type property paths. And probably the most subtle and most meaningful change here is that we give semantics to our code, to our queries. What has been previously text is now code. And this is a very strong indicator and this very strong
change. Imagine what what's now possible. essentially being code your IDE is participating in refactorings. Your compiler immediately tells you things that do not fit together. You get immediate feedback not just from your compiler but also from your IDE that is hinting you that something is is not right. But typically, if you start to refactor a method name or property name, the IDE considers all those places where
such a method is being used and referenced and the IDE refactoring takes care of exactly those so that you no longer run into orphaned and missing property references. No surprises Much faster feedback testing is really in that becomes in that sense a rather late feedback because the much earlier feedback that you get is an immediate one from your editor. We aren't introducing something new to what you
already have. We are re-reusing the domain model that is your single source of truth. We do not generate any meta model or the like. No plugins, no annotation processors. Everything is a library feature. And that makes really Spring Data an ideal point for integrating such a feature because we have many many models, many modules that do something with data, that host a query or even update API
where you can express property and all of these can benefit from typed property paths immediately. So this makes it a great point for integration and allows us to to participate for every single module that decides to upgrade and to adopt those type property paths. And practically speaking, we have support already for MongoDB, for Spring Data JDBC, for Spring Data Cassandra, and Couchbase and that provides those APIs.
We have experimented and explored ways to integrate Spring Data JPA and the criteria-based API with the type-based property paths. But we are not convinced yet that we found a sweet spot there, but rather to the already present level of complexity how to define specifications and the criteria API, we would add yet another layer that makes your code much more verbose and makes the interesting parts in your
query hard to spot. And we feel that this is not up to the level up to the standard that we see as modern-day programming. I've been talking quite a bit about type context and type Now, previously when expressing a property through a string, we don't have any typing information attached. We just have the bare name of the of the property. With type property paths, that's going to
change. We suddenly get a type context that we can leverage in various ways. First and foremost is to get the what domain model and what domain type are we talking about. And every time you see declarations, you see immediately the type that is associated, whether it's the root owning type or whether the those are the types being involved in the chain of of properties. And this contextual
information is pretty useful when we take that model one step ahead and start making use of this type What does this mean in in a practical sense? So, you might be familiar with the sort API that Spring Data is providing. Typically, when you define a sort, you specify a number of properties that you want to sort by if you use the varargs style. We can have the
very same with type but the type property path has two generic parameters. It's the T type, which is denoting the root owning type, and it has a P type, which expresses the property type. So, basically, the type that comes out of that property path and which shows the leaf property For a user get age, typically that would be T would be the user and age some numeric
type such as an integer. And if you start chaining like the previous example, user get addresses and then address get city, then T remains user and city probably is going to be a string. And the information that there was an intermediate address element is not being reflected in the type signature at For now, we don't need it. So, what can we do with that? In a sort
arrangement and in many, many other cases as well, we typically expect those properties to originate from the very same type that we are already operating in. It would be kind of weird to sort by username and then order ID while order ID comes from from an totally independent order object. And we can express that as a constraint with generic bounds. With ignoring the actual property type in
in sorting and constraining the origin domain type to be consistent, through the compiler and through the IDE to recognize whether you have been using the same as an origin for for sort properties or if you decide to use a different type, then the IDE will immediately give a compile error and showing that something is not not right. That's obviously not everywhere applicable, but it is it makes
a lot of sense in those areas where it is applicable, whether you are expressing sorting and currently not considering joins, right? So, that is that is an different field. But everything that happens within the scope of a single entity, a single domain object, a single aggregate root can benefit from such arrangements. It can be projections. It can be in some future revision even helping with constraining the
query type and typing the queries. But we are not yet there and we still have to learn whether it even makes sense to promote this concept to higher-level constructs like queries to to propagate the domain type throughout the query or whether this is this sounds on paper nice and in practicality, it just hinders us from writing good queries. So, therefore, you will find those kind of features
only sporadically. Probably, couple of you have studied thoroughly the abstract of this talk. And if so, you might have noticed that I promised you a bit of magic. So, honestly talking, this this looks weird, in such context, that's nothing we've we've seen too much before. You know, magic is a thing for mostly for entertainment purposes. Our understanding is slow. Our perception is fast. And magic is the
art of managing that gap between and perception. So, let's have a look at internals. And it's after all not that complex. You've probably noticed already those method references are using lambda And indeed, they are lambdas. Each expression is its own lambda instance and those are static instances. And Java is going to create a single object instance no matter how often you run that method, right? So, that
that is one of the beauties that we get immediately out of Java. However, if you write twice the same expression next to each other, those will be still two instances. Those are the rules of of And behind the scenes, what happens is that we are leveraging serialization to serialize lambdas the regular Java mechanism that is already in place for serializing and creating lambdas in in the first
place. So, we are not using any unsafe API that is recommended to migrate off, but rather well-documented, well-specified facilities that are already in place wherever we are using functional programming. Type property paths are internally parsed for what they are trying to We also allow the lambda form of expressing property references, but this comes at a much higher cost. Method references are much simpler to parse and all
the information is available immediately. So, much much polished performance baseline in in that regard. we see such a lambda, uh we first check our internal cache to avoid constant re-parsing of those property references, which will give you overall a very decent performance baseline. Clearly, if you start benchmarking your application against the string-based version, the string-based version is going to win in any case, always. the lambda-based version
is close. We are resolving those property paths only once after the very first usage. There is though a little bit of Kotlin specifics and the interesting bits about Kotlin is that Kotlin 1 1.x and this depends on the language which level represents those K properties differently than Kotlin 2 does. And I think there will be some evolution and we are keen to adopt with the latest evolution
of the Kotlin language spec itself, but things might break. They are not as well specified and understood towards the Java community as for instance the lambda meta factory is. So, there is always a little bit of movement, but rest assured, we will have you covered to ensure that with the next Kotlin version, when something is going to change, we're going going to notice that as well because
we are staying top notch on on most recent dependencies and most recent language versions with within our own systems. So, ideally you should not notice. tooling support already in Spring Tool Suite um in for supporting uh VS Code, Eclipse, um Theia. Uh our Spring Tools team already started implementing and exploring support how su- can provide you with suggestions for code areas where the tooling has already sufficient
context to guide you towards property path usage. It will give you uh a quick hint and inlay hint and allow you to transform string-based properties into method references This is clearly not always possible because sometimes query code is distributed across many methods. In some cases, the same query is being used for different queries with different domain types. And in such cases, you're probably better off with staying
either on or you start splitting your queries. However, this is not a must. This is an additional option make trying to make your lives more convenient. IntelliJ uh is a JetBrains uh with IntelliJ is also looking in uh supporting those kinds of features. Um but we're talking really early stages about type property paths and and tooling support here. Let me provide you a little bit of guidance
how to get from where we are today to where you could be tomorrow. Probably the strongest advice that I can provide you with is if there's nothing really wrong with your code today, then probably you're already in an optimum place. If you however feel there's a lot of movement across the domain, you have suffered from refactoring issues that popped up really late in the process, then you
might want to consider exploring type break Wherever you have a domain model and really that is the prerequisite for entering the space of type property then type property paths might be a good alternative for you to give it a try and start small. Type property paths are by no means a replacement for Duke, QueryDSL, or a JPA meta model. It's it's quite the All of these, QueryDSL
and the meta model and the criteria API themselves are utilities to build queries, to express predicates, to adopt uh the native functionality of the underlying data store. solely a way to replace brittle string references with something more robust and to give your code a little bit more expressivity. Everything else remains really the same. If you suffer from readability issues because of too much strings, then type property
paths can be something for you. Though you have already seen in essence, they are they produce a little bit more and more and longer expressions. So, there's always a trade-off and a and a balance and finally it's it's your call to judge how much of a benefit you get out of such a functionality versus what does it take away in your code and how much harm and
how much complexity does it already create and contribute to the already present complexity. I've mentioned Kotlin a couple of times. If you're a Kotlin user and have Kotlin code, then you can immediately start using type property paths with even a more refined syntax without uh all the inconvenience that you have seen on the Java side navigate uh through various levels of domain properties and to navigate your
You imp- you import your ex- those extension functions and there you go. And you are using the native syntax to express properties within the Kotlin realm. Generally, this is a rather transformative change. introducing controversial that may or may not survive. And I guess the same same is true here. We have to see where all this goes. For new code, I think I would start For existing code,
that might be a different story. You might want to ex- start exploring and putting type property paths into areas where they make sense. And even maybe go to start.spring.io and start playing with that, right? There is however one additional restriction that you should should be really aware of. In a few scenarios, there is quite a bit of dynamic involved with uh expressing sort, applying sort, applying queries.
For instance, if you get an information about a property from somewhere else like a web request, a query parameter in an HTTP request that translates to a sort by parameter, uh have a form that expresses various properties after which you wanted to filter. Since those are already strings, there's no way to back to translate those strings into the method reference form. the string-based uh representation of strings
is your best option. And again, the string-based API is here to stay. The type property paths are only an addition for you as an if it makes sense. How can we get hold of that? So, we've released last month a pretty stable preview version, Spring Data 2026.0.0-M2. Friday this week, we're going to enter the release candidate phase. What does this mean for you? You are our channel
to get feedback. You are our users. You are our community that care about. If you get the chance to upgrade, to play around with type property paths, please do so. We've prepared a couple of Spring Data examples that you uh you are immediately available for your consumption that can help you in guiding and understanding how type type property paths are being used, even for the for the
JPA part that we might even remove if we feel that this is not not the right approach, but at least we have learned something. So, the message the takeaway message for you here is start take this, start playing around, give it a try, learn whether this is the new Spring way to approach modern programming, how to queries, how to bring back the joy into and to address
the complexity and the needs that we have already today that just sparks joy. And with that, thank you for attending. It has been a pleasure. And I'm wishing you a very great remaining SpringIO. >> [applause]
More from this event
See all 38 talks →
Spring I/O 2026 Keynote
1:08:44
The Spring AI Ecosystem in 2026: From Foundations to Agents @ Spring I/O 2026
43:39
Breaching LLM-Powered Applications: Overcoming Security and Privacy Challenges by Brian Vermeer
48:40
New in Spring Security 7: MFA, OAuth2 and more by Daniel Garnier @ Spring I/O 2026
46:43