Spring Boot best practices and Spring pitfalls
These are some of the best practices we collected in our Spring projects. Here a small list of what we usually forget to check at the start of the projects.
- Set spring.jpa.open-in-view=false
- Stay up-to-date with Spring releases
- Virtual Threads
- Server compression
- Cache
- @Autowired Injection
- Avoid the use of @Value in Spring Boot
- Document your REST APIs and the errors
- Always Explicitly Name Parameters for
@RequestParam
,@PathVariable
and@Param
- Interfaces and Proxies
- Java related practices
Set spring.jpa.open-in-view=false
By default, Spring Boot registers an OpenEntityManagerInViewInterceptor (spring.jpa.open-in-view=true
).
With this option, Spring Boot binds a JPA EntityManager to the thread for the entire processing of the request.
This practice was useful in the past (using JSP and other view renderers) to avoid the LazyInitializationException when some entities were retrieved in a transaction and, in subsequent code, the application tried to access some lazy loaded entities linked to the result.
At this point the transaction was already closed and the entities detached.
For most applications this feature is not necessary anymore, some developers wanted to disable it by default in Spring Boot 2 already.
This generated a debate between 'java gurus' on the utility and the performance impact of this binding.
I recommend to start a project setting spring.jpa.open-in-view=false
and activate it if necessary. For REST applications with a transactions managed at service level this interceptor should not be necessary.
De-activating open-in-view
will avoid to bind an entity manager to the thread at every request and unbind it at the end of the request. The code concerned is in the org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor
class.
Stay up-to-date with Spring releases
You can find the dates of the releases here: https://calendar.spring.io, it's useful to check when some new releases are planned. Don't expect that the latest bugfix release won't create issues, before deploying to production runs all the usual tests. Often the new bugfix releases introduced new bugs breaking our development environment. Spring in minor releases updated the dependent libraries, this could fix some CVE issue but break your application.
Virtual Threads
If you are using Java 21 or later and Spring 3.2 you can activate Virtual Threads in Spring using: spring.threads.virtual.enabled=true
If your application has a lot of web requests or is using @Async methods you could see an improvement in the performances.
Traditionally, each web request open a new thread at OS level, this operation is expensive. With virtual threads the Java Runtime will create a light thread with an improved utilisation of the resources.
Server compression
This feature can be useful if your deployment doesn't use a proxy (e.g. nginx), we can ask Spring to compress the static assets and reduce the size of the files sent to the client.
# Enable response compression
server.compression.enabled=true
# Mime types that should be compressed
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
# Minimum response where compression will be done
server.compression.min-response-size=8192
spring.web.resources.cache.cachecontrol.max-age=15768000
The best solution is still to use a dedicated proxy server with caching features.
You can find an example in my post: Docker with Angular and Nginx
Cache
Check if your view template engine supports caching in Spring and use it in case of benefits. Example, this blog mustache and the caching has to be activated: spring.mustache.servlet.cache=true
@Autowired Injection
Nowadays the IDE should flag this as an issue. In some old codebase you could still find @Autowired.
Avoid field and setter injection
It should be used only for optional dependencies or in test code.
Good
- easy to use
Bad
- the bean can be changed runtime with a new call of the setter;
- the field can reference a null instance, you should add the annotation @Required to enforce the dependency;
- it's easy to add dependencies, the class risk to violate the single responsibility principle becoming a container of
services.
References
- Olivier Gierke: why-field-injection-is-evil
Use constructor injection
Good
- the beans cannot be null;
- the object is immutable;
- the object can be defined final;
- in case the bean has only one constructor you can omit @Autowired;
- force to better think the responsibility of the class.
References
- Spring Blog: How not to hate spring in 2016
- Spring Documentation: Reference
If you are using lombok you can use @AllArgsConstructor
or @RequiredArgsConstructor
and simply declare your components as fields.
Lombok will create the constructor that will be used by Spring to inject the components.

Avoid the use of @Value in Spring Boot
Spring Boot introduced the @ConfigurationProperties annotation that is 'far more superior than the basic @Value approach' according to Stéphane Nicoll (Pivotal).
The advantages:
- You inject only an object a POJO and not a list of fields
- There is less risk to do typos in the declaration of the property
- The POJO is TypeSafe and can contain complex structures (e.g. 'database.configuration.mysql.connection')
Here you can find the documentation:
- Spring Boot: Type-safe Configuration Properties
Document your REST APIs and the errors
Your APIs could be used by other teams, add some documentation to them. You can use Swagger or Spring Rest Doc.
In version 6.0 spring added the ProblemDetail
class for an extended error response.
Always Explicitly Name Parameters for @RequestParam
, @PathVariable
and @Param
When working with Spring MVC and Spring Data JPA, it's a best practice to explicitly declare the names of your parameters in annotations like @RequestParam
, @PathVariable
, and @Param
.
Spring MVC Example
@GetMapping("/users")
public String getUserById(@RequestParam("id") String userId) {
return "User ID: " + userId;
}
By specifying the parameter name ("id"), you're making the binding explicit. This ensures Spring correctly maps the request parameter to your method argument—regardless of how the code is compiled or whether the -parameters
flag is used.
If you omit the name, Spring will attempt to infer it from the method signature, which only works if your code is compiled with -parameters
.
While enabling -parameters
allows Spring to infer names via reflection, it’s still safer and clearer to be explicit—especially in larger or collaborative codebases.
Spring Data JPA Example
Not good (positional binding):
@Query("SELECT u FROM User u WHERE u.username = ?1 AND u.code = ?2")
List<User> findByUsernameAndCode(String username, String code);
Better (explicit named binding):
@Query("SELECT u FROM User u WHERE u.username = :username AND u.code = :code")
List<User> findByUsernameAndCode(@Param("username") String username, @Param("code") String code);
Using @Param
with named placeholders makes it obvious which method argument maps to which part.
What is -parameters?
Java, by default, strips method parameter names during compilation unless you explicitly tell it not to.
public void greet(String name) { ... }
Without -parameters, reflection sees:
greet(java.lang.String arg0)
With -parameters, it sees:
greet(java.lang.String name)
This matters because Spring MVC and Spring Data JPA use reflection to map method parameters to inputs from HTTP requests or database queries.
Interfaces and Proxies
Use Interfaces whenever it is possible and avoid CGLIB
Interfaces allow clean code debugging and a faster and smaller deployment. Here you can find more details: https://marmo.dev/spring-interfaces-cglib
Understand how proxies works
Spring proxies your classes and the annotations work on the proxied class.
For this reason, e.g., passing through 2 or more @Transactional
methods without an external call between the 2, won't separate the Transaction.
Java related practices
Use records instead of beans for immutable data
records are a great feature that allows you to easily create immutable data objects. Prefer them to classic beans or
lombok data objects if you need to return data from repositories, or you need to transfer data using REST APIs.