Spring is a very rich set of frameworks and libraries. This means that it may be easy to miss some useful features or best practices. In this series of articles, I will share some tips that can help you to improve your Spring applications.
These tips will be short and practical. They will cover various aspects of Spring application development, such as configuration, coding, performances, tests, etc.
The articles in this series:
Let's begin with a basic tip about Spring Boot logging.
When you start your Spring Boot application, a default banner is logged:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.3.1)
INFO 11324 [ main] c.adeliosys.mockup.MockupApplication : Starting MockupApplication using Java 17.0.7 with PID 11324
I usually prefer more compact logs. Fortunately, the banner can be disabled with a single configuration
parameter, for example in your application.properties:
spring.main.banner-mode=off
The application logs are now shorter:
INFO 3560 [ main] c.adeliosys.mockup.MockupApplication : Starting MockupApplication using Java 17.0.7 with PID 3560
Or you can easily use your own banner. To do so, simply add a src/main/resources/banner.txt file with the
banner content:
______ _
| ___ \ | |
| | _ | |_ _ | | _ ____ ____ ____ ____ ____
| || || | | | | | || \ / _ | _ \| _ \ / _ )/ ___)
| || || | |_| | | |_) | ( | | | | | | | ( (/ /| |
|_||_||_|\__ | |____/ \_||_|_| |_|_| |_|\____)_|
(____/
INFO 13032 [ main] c.adeliosys.mockup.MockupApplication : Starting MockupApplication using Java 17.0.7 with PID 13032
There are many online tools that can generate a nice banner, you may for example search for ascii banner generator.
Update note: starting from Spring Boot 3.4, the shutdown is now graceful by default, but can be configured as immediate if needed.
When a Spring Boot application is stopped, an immediate shutdown is performed by default. This means that running business processes may be interrupted. This could cause errors and integrity issues. We usually do not want that, especially in production.
Instead, you can enable a graceful shutdown. This will reject new requests (connection refused or 503 error, depending on the embedded web server), and wait (up to a configurable maximum duration) for the current requests to complete.
Sample configuration for your application.properties:
# Enable graceful shutdown
server.shutdown=graceful
# Configure the grace period (defaults to 30 seconds)
spring.lifecycle.timeout-per-shutdown-phase=20s
When enabled, the graceful shutdown is visible in the application logs:
INFO 2476 [ionShutdownHook] o.s.b.w.e.tomcat.GracefulShutdown : Commencing graceful shutdown. Waiting for active requests to complete
INFO 2476 [tomcat-shutdown] o.s.b.w.e.tomcat.GracefulShutdown : Graceful shutdown complete
When developing a Spring based application, not everything is a Spring bean. Some business classes may not be managed by Spring, for example some callbacks for various frameworks.
But what if we need to call a Spring bean from these classes? We should not directly instantiate the Spring bean class,
because doing so would result in a regular object with neither dependency injection nor working interceptors
(such as @Transactional). Instead, we need a way to obtain the bean from the Spring application context.
Fortunately, we can easily implement a utility class that provides Spring beans:
@Component
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext context;
public void setApplicationContext(ApplicationContext applicationContext) {
context = applicationContext;
}
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
}
This utility class is a Spring bean, thanks to @Component. It implements ApplicationContextAware so after
initialization, Spring will call its setApplicationContext method with the current application content.
We store it in a static attribute, so it can be used from the static getBean method.
Since that method is static it can be called very easily.
Now, in your non-Spring class or method, you can obtain and use a Spring bean like this:
MySpringBean mySpringBean = ApplicationContextUtil.getBean(MySpringBean.class);
mySpringBean.someMethod();
You can also expand the ApplicationContextUtil class with additional methods from the Spring ApplicationContext.
This section is an enriched extract from my Hibernate logging and monitoring guide where I explain how to use Hibernate logs and metrics to troubleshoot and optimize the persistence layer of your application.
Spring Boot uses Hikari as the default relational database connection pool. It is important to understand its configuration parameters to improve the performances and reliability of your applications.
I will describe some of the major configuration parameters and provide samples for your application.properties.
Of course, the values must be adapted to your application requirements and usage.
We usually start by defining the maximum number of connections in the pool. The default value is 10.
spring.datasource.hikari.maximum-pool-size=20
Then we choose between a fixed size pool and a shrinking pool. A fixed size pool is a reasonable default. It means
that all connections are created when the pool starts and will remain in the pool (connections are actually
periodically recreated as described later). This ensures better performances but costs more resources. In some cases,
for example when you have many microservices, you may want your pools to shrink after a while when the connections
are not used, to free up some database resources. If you want a shrinking pool, configure the minimum pool size
and the connection maximum idle duration (defaults to 600000 milliseconds, i.e. 10 minutes):
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.idle-timeout=300000
If there is no available connection in the pool when the application (Hibernate actually) asks for a connection,
then the pool will wait, up to a certain timeout (defaults to 30000 milliseconds, i.e. 30 seconds),
for a connection to become available. If the timeout is reached, then the pool will throw an exception.
Configuring the timeout is a trade-off between waiting and failing.
spring.datasource.hikari.connection-timeout=10000
Some infrastructure elements (routers, firewalls, etc) do not like network connections that remain open for a long
duration. In that case you can configure the maximum duration of your connections. When it is reached,
the connection is closed and a new one opened. The default duration is 1800000 in milliseconds (30 minutes).
spring.datasource.hikari.max-lifetime=3500000
This selection is only a subset of the configuration parameters, for a complete list and additional information see HikariCP documentation.
This section is an adapted extract from my Getting started with Spring tests article where I share opinionated best practices to write integration tests with Spring.
When writing tests, we usually make sure that the test methods are independent and do not interfere. For integration tests that use a database, this means being careful on how the data are created and reset.
One way to do so with JUnit is using @BeforeEach and @AfterEach methods to initialize and cleanup the data for each
test method.
Or you can use @BeforeAll and @AfterAll methods to initialize and cleanup the data once per test class,
and annotate the test methods with @Transactional:
@SpringBootTest
public class SampleTest {
@BeforeAll
static void beforeAll() {
// Initialize the database
}
@AfterAll
static void afterAll() {
// Cleanup the database
}
@Test
@Transactional // This rolls back the transaction at the end of the test method
public void myTest() {
// Test code
}
// Other test methods
}
When used on a test method, @Transactional will open a transaction for the test method (it also includes the
@BeforeEach and @AfterEach methods) that will be automatically rolled back. This means that all database
modifications will be reverted.
I usually prefer this approach. It is simpler, less error-prone and a bit faster since we perform fewer database calls.
Note that you can set the @Transactional annotation at the class level instead of setting it on each test method.
There are many more Spring tips, tricks and best practices out there, and I hope you enjoyed this first selection. Stay tuned for the next part of this series!
© 2007-2025 Florian Beaufumé