Spring I/O 2025 Conference notes and takeaways
🔧 Spring Framework 7 & Spring Boot 4
🚀 Key Features and Trends to Watch:
- Built-in Web Clients: Simplified HTTP communication with native support.
- Improved API Versioning: Enhanced strategies for managing evolving APIs cleanly.
- Spring AI: A new initiative integrating AI capabilities into the Spring ecosystem.
- Faster Derived Queries in Spring Data JPA 4: Significant performance improvements for query generation.
- Project Leyden: Focused on improving startup time, memory usage, and packaging through static image generation and AOT (ahead-of-time) compilation.
☕ Java Platform: JDK 25 (LTS)
JDK 25, scheduled for release in September 2025, will be the next Long-Term Support (LTS) version of Java. For teams currently migrating from Java 8 to Java 17, it’s worth considering whether to:
- Complete the upgrade to Java 17 now, or
- Plan for a direct transition to Java 25 LTS, given its extended support horizon and potential alignment with newer frameworks and tools.
While Java 17 is stable and widely supported (including by current Spring versions), Java 25 may become the de facto standard for enterprise applications over the next several years. A strategic upgrade path should be considered based on team velocity, release cycles, and framework compatibility.
🌱 Spring Ecosystem: What’s Coming
- Spring Framework 7 and Spring Boot 4 are expected in November 2025.
- Both will support Java 17 and newer, making an upgrade to Java 17 a practical prerequisite.
- These releases will introduce modern APIs, enhanced support for reactive and AI-driven applications, and deeper native image capabilities (GraalVM & Leyden alignment).
✅ Recommendation
If your team is already in the process of migrating to Java 17, it makes sense to complete the transition, especially to align with the upcoming Spring 7/Boot 4 releases. However, it’s also wise to begin evaluating JDK 25 early and monitor its feature set, as it may offer substantial benefits and will be the next major LTS release.
Resources:
🧙 Demystifying Spring Boot Auto-Configuration
- Spring Boot applications have a lot of built in dependencies that you may not be using. The speakers talks about writing your own Spring Boot starters that are only going to include the parts we need, making the applications lighter. Such starters can also include all the other dependencies that you may need so if you have a lot of services that share the same dependencides then you can use the starter in all of them. Write once, use everywhere.
Resources:
- Custom Spring Boot Starters – Medium article
- Reddit discussion on custom starters
- (Pending) Auto-Configuration Workshop video on YouTube
⚠️ Top REST API Design Pitfalls (and How to Avoid Them)
🧱 1. Backwards Compatibility is Crucial
- Don’t break clients — changing field names, enum values, or required fields can have unexpected downstream effects.
- Instead of changing, add optional fields or values.
- At some point, if tech debt piles up, introduce versioning (but try to avoid maintaining many versions in parallel).
- 💡 Tip: Use tools like
Pact.io
for consumer-driven contract testing to understand what clients depend on.
🔃 2. Versioning APIs
- Prefer
/v2
in URLs for clarity. - Radical idea: Instead of versioning an existing service, create a new service with a new DB, and decommission the old one (“design for deletion”).
⚠️ 3. Detecting Breaking Changes
- Compare generated OpenAPI (Swagger) files across commits to catch accidental contract changes.
- Tools: Spring Cloud Contract, Pact.io, Git diffs.
🧪 4. Separate Contract from Implementation
- Never expose your internal domain model directly in the API.
- Always create separate DTOs for requests and responses to avoid tight coupling.
🔁 5. CQRS at the API Level
- Use different DTOs for GET vs POST/PUT.
- Principle: Commands change state, queries return data.
- Example: Creating an inventory item shouldn’t reuse the DTO used for viewing it.
🔁 6. Don’t Return Data from PUT
- A
PUT
should update; if the client needs data, they should follow it with aGET
. - Returning data on write operations can lead to tight coupling and security issues.
🧭 7. Batching for Performance
- Avoid
getById
spam (e.g. 1M calls); instead, offer batch or paginated endpoints. - Don’t expose sequential IDs → leads to ID scanning vulnerabilities.
- Use
UUID
s.
🧱 8. Avoid Stateless Assumptions
- Stateful pagination (e.g. “next page” without parameters) leads to load balancing nightmares.
- Maintain statelessness where possible.
⚙️ 9. Error Handling Best Practices
- Don’t return stack traces or internal errors to clients (security risk).
- Use trace IDs and return user-friendly error messages.
- Validate and return all errors at once (not one by one).
🧠 10. Avoid Automapper Coupling
- Don’t tie your DTOs so tightly that a name change breaks builds.
- Prefer explicit mappers or use
MapStruct
(auto-generates safe and inspectable code).
🖼️ 11. UX & Endpoint Design
- Large forms with all fields editable → race conditions (e.g., stale stock).
- Use segmented endpoints/actions for editing specific fields (e.g.
deactivate
,update price
). - Prefer buttons with verbs over generic
PUT
s (semantic clarity).
🧨 12. Don’t Use PATCH Carelessly
PATCH
is semantically vague and difficult to maintain.- Use
JSON Patch
if needed, but even that can be messy. - Prefer clear, intent-driven endpoints.
🛡️ 13. Security Considerations
- Exposing domain models risks leaking sensitive data (e.g., GDPR violations).
- Avoid using internal enums or stack traces in responses.
Resources:
- Victor Rentea - Top REST API Pitfalls
- CQRS whitepaper (PDF)
- Victor Rentea – Talk
- Follow-up talk
- Designing Better APIs – YouTube
- Baeldung – ShedLock for distributed scheduling
🧵 Spring Modulith and AsyncAPI: Event-Driven Done Right?
- Overview of
@Async
,@EventListener
,@TransactionalEventListener
- Using Spring Modulith and AsyncAPI for modular eventing
Resources:
💀 Hibernate & Spring JPA Pitfalls That Might Be Killing Your App Performanc
🧩 1. Use the following property when running/testing locally: spring.jpa.properties.hibernate.jdbc.generate_statistics = true
✅ What it does:
- Enables Hibernate’s internal performance statistics:
- Number of SQL queries executed
- Time each query takes
- Cache hit/miss rates
- Flush, load, insert operations count
📌 Why it’s useful:
- Identify the N+1 problem
- Validate if second-level or query caching is working
- Understand Hibernate behavior for optimization
⚠️ Warning:
- Do not use in production — high overhead
- Use in development or testing environments
🔗 2. Use Set
Instead of List
for @ManyToMany
Relationships
✅ Benefits of Set
:
- Enforces uniqueness of related entities
- Prevents duplicate inserts in join tables
- Avoids
MultipleBagFetchException
caused by using multipleList
s in fetch
⚠️ Important:
- Always implement
equals()
andhashCode()
in your entity class to ensureSet
behaves correctly
🐞 3. Avoid MultipleBagFetchException
⚠️ What is it?
- Thrown by Hibernate when you eagerly fetch multiple
List
-based@OneToMany
or@ManyToMany
associations - Hibernate cannot manage multiple bags (lists) in a single fetch join
✅ Solutions:
- Use
Set
instead ofList
for these associations - Use lazy loading and fetch relationships in separate queries
- Use DTO projections or
@NamedEntityGraph
to control fetch depth
⚙️ 4. @Cacheable
Doesn’t Work With Custom @Query
Methods
❌ Problem:
- Spring’s
@Cacheable
works at the service layer, not on JPA repositories with@Query
- Queries defined with
@Query
are not cached by default
✅ Hibernate Query Caching Workaround:
@Query("SELECT e FROM Entity e WHERE e.name = :name")
@QueryHints(@QueryHint(name = HINT_CACHEABLE, value = "true"))
List<Entity> findByName(@Param("name") String name);
- This enables support for query caching in the newer Hibernate versions
🚫 5. Don’t Use CascadeType.REMOVE
on @ManyToMany
❌ Why it’s dangerous:
- It deletes the target entities, not just the association in the join table
- This can lead to data loss, especially if other entities still reference the same target
✅ Example:
- If
User A
andUser B
both haveRole R
, deletingUser A
withCascadeType.REMOVE
could also deleteRole R
, affectingUser B
✅ Better Practices:
- Don’t use
CascadeType.REMOVE
on@ManyToMany
- Manually manage join table entries
- Consider
CascadeType.DETACH
orCascadeType.MERGE
as alternatives
Resources:
🔐 Spring Security: What’s New in 6.5?
- Passkey (WebAuthn) support in a few steps
- JWT + access token support in Bearer auth
- IntelliJ HTTP client as Postman replacement
Resources:
🚀 GraalVM: Worth the Hype for Spring Apps?
- Native image generation pros and cons
- Licensing and JIT vs AOT for Spring
Resources:
🧩 Legacy DDD Applications and Dynamic Admin UIs
Apache Causeway dynamically generates a UI and REST API for your domain objects using conventions and annotations in a Spring Boot application.
🛠️ What Apache Causeway Does
Apache Causeway (formerly known as Apache Isis) is a rapid application development framework for building backend-heavy business systems in Java, especially when:
- The domain model is the heart of the application.
- You don’t need a complex or heavily customized UI.
- You want a REST API and a working UI with minimal effort.
✅ Features:
- Generates:
- A responsive, CRUD-based web UI using Wicket viewer.
- A complete REST API (RESTful Objects spec) from your domain classes.
- Offers built-in support for:
- Entity persistence (via JPA)
- Validation and business rules
- Auditing, command logs, publishing
- Extensions like Excel export, PDF, and scheduling
- Built on Spring Boot, JPA, and other modern tools.
- Uses annotations (
@Property
,@Action
,@DomainObject
) to configure behavior.
Resources:
🌐 Building Resilient Distributed Systems with Restate
Restate is a modern, open-source platform designed to simplify the development of resilient, distributed applications. It offers a durable execution engine that allows developers to write reliable services and workflows using familiar programming paradigms, without the need for complex infrastructure management.
🚀 Key Features
- Durable Execution: Restate ensures that your application logic is automatically resilient to failures such as crashes, network issues, and timeouts. It achieves this by journaling function executions, allowing for safe retries and recovery without duplicating side effects.
- Virtual Objects: These are stateful entities that maintain persistent state across invocations, enabling developers to model long-lived business processes and entities with ease.
- Durable Promises: Restate provides a mechanism for transparent and fault-tolerant communication across services and processes, allowing asynchronous operations to be composed reliably over time. orkflows-as-Code: Developers can implement complex workflows using standard programming languages, benefiting from Restate’s ability to handle retries, state persistence, and timing, all without requiring external workflow engines or DSLs.
- Single Binary Deployment: Built in Rust, Restate offers a lightweight, single-binary deployment model with no external dependencies, making it easy to run locally, on-premises, or in the cloud.
Resources: