Spring Boot Application Properties
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
Introduction
In this article, I’m going to show you the best way to configure the Spring Boot Application Properties file.
This is not just theoretical advice. I applied all these tips when developing RevoGain, a web application that allows you to calculate the gains you realized while trading stocks, commodities, or crypto using Revolut.
Spring Boot Application Properties file
When Spring Boot emerged, it came up with a very clever idea of aggregating all configurations in a single application.properties
file.
You can either use a Java Properties file or a YAML one, but I always choose the Properties file format because I don’t have a scale ruler to fix YAML indentation issues:
Software developer fixing a YAML file pic.twitter.com/bxGMLqAyeU
— Vlad Mihalcea (@vlad_mihalcea) August 27, 2021
Spring Boot Application Properties – Web Configuration
Here’s the list of settings I use to configure the Web layer:
## Web error page server.error.whitelabel.enabled=false ## Web HTTPS settings server.tomcat.remoteip.remote-ip-header=x-forwarded-for server.tomcat.remoteip.protocol-header=x-forwarded-proto ### Web Gzip server.compression.enabled=true server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css ## Web static resources versioning spring.web.resources.chain.strategy.content.enabled=true spring.web.resources.chain.strategy.content.paths=/js/**,/css/** ### Web caching spring.web.resources.cache.cachecontrol.max-age=30d
The server.error.whitelabel.enabled
property disables the default Whitelabel Error Page so that we can customize an application-specific error page.
The next step is to have an error handler that allows you to customize the error page for authenticated and non-authenticated users:
@Controller public class ApplicationErrorController extends BaseController implements ErrorController { @RequestMapping("/error") public String handleError( Model model, HttpServletRequest request) { Object statusCodeValue = request.getAttribute( RequestDispatcher.ERROR_STATUS_CODE ); String errorMessage = null; String requestPath = (String) request.getAttribute RequestDispatcher.ERROR_REQUEST_URI ); if (statusCodeValue != null) { int statusCode = Integer.parseInt(statusCodeValue.toString()); if (statusCode == HttpStatus.NOT_FOUND.value()) { errorMessage = String.format( "The [%s] request could not be found.", requestPath ); } else if (statusCode == HttpStatus.INTERNAL_SERVER_ERROR.value()) { Object exception = request.getAttribute( RequestDispatcher.ERROR_EXCEPTION ); if(exception instanceof Throwable) { String errorCause = ExceptionUtils .getRootCause( (Throwable) exception ) .getMessage(); errorMessage = String.format( "The [%s] request could not be processed - %s", requestPath, errorCause ); } else { errorMessage = String.format( "The [%s] request could not be processed.", requestPath ); } } else { HttpStatus status = HttpStatus.valueOf(statusCode); errorMessage = String.format( "The [%s] request failed with this status code: %s", requestPath, status ); } } if(errorMessage != null) { model.addAttribute("error", errorMessage); } LOGGER.error(errorMessage); return UserContext.getCurrentUser() != null ? "error-logged-in" : "error"; } }
The error-logged-in.html
is a Thymeleaf page that’s displayed for authenticated users, and the error.html
is the Thymeleaf page for non-authenticated users. The reason I use two error pages is that the layout and especially the menu differs between the initial landing pages and the actual application layout.
The server.tomcat.remoteip
settings are used to enable HTTPS when running your Spring Boot application behind a proxy server, as it’s the case on AWS:
server.tomcat.remoteip.remote-ip-header=x-forwarded-for server.tomcat.remoteip.protocol-header=x-forwarded-proto
You should only use HTTPS for all your websites and web application available over the Internet. Not only is the communication secured, but Google is going to increase the ranking of your web pages as well.
The web compression settings are used to enable HTTP compression using GZIP for the underlying Tomcat server used by Spring Boot:
server.compression.enabled=true server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain,application/javascript,text/css
The resource versioning settings allow you to avoid caching issues when modifying JS and CSS resources and redeploying your application:
spring.web.resources.chain.strategy.content.enabled=true spring.web.resources.chain.strategy.content.paths=/js/**,/css/**
So, with web resource versioning, a Thymeleaf link like this one:
<link rel="stylesheet" th:href=@{/css/main.css}/>
is going to be rendered like this:
<link rel="stylesheet" href=/css/main-894af16207c18178542fdc5f96f46a2b.css/>
The 894af16207c18178542fdc5f96f46a2b
suffix is a hash value generated based on the main.css
content. If you change the file content, the hash value will change as well, meaning that you are going to invalidate the old cached entries since this file is going to be fetched from the server the first time a user accesses this page.
The cache.cachecontrol.max-age
setting is used for caching web resources for 30 days:
spring.web.resources.cache.cachecontrol.max-age=30d
Since we are using resource versioning, we can set a longer cache period for our web resources and make the page rendering faster since less content will be needed to be fetched from the webserver.
Spring Boot Application Properties – Database Configuration
The data access layer has the most significant impact on application performance. Hence, it’s very important to pay attention to how we configure it.
This is a list of settings that you can use if you are using MySQL and HikariCP in your Spring Boot application:
## DataSource properties spring.datasource.url=jdbc:mysql://localhost:3306/revogain spring.datasource.username=${REVOGAIN_DB_USER} spring.datasource.password=${REVOGAIN_DB_PASSWORD} ## HikariCP configuration spring.datasource.hikari.minimumIdle=0 spring.datasource.hikari.maximum-pool-size=40 spring.datasource.hikari.maxLifetime=900000 spring.datasource.hikari.transaction-isolation=TRANSACTION_READ_COMMITTED spring.datasource.hikari.auto-commit=false spring.datasource.hikari.data-source-properties.useServerPrepStmts=false spring.datasource.hikari.data-source-properties.cachePrepStmts=true spring.datasource.hikari.data-source-properties.prepStmtCacheSize=500 spring.datasource.hikari.data-source-properties.prepStmtCacheSqlLimit=1024
The minimum connection pool size is 0
, and it can grow to at most 40
connections:
spring.datasource.hikari.minimumIdle=0 spring.datasource.hikari.maximum-pool-size=40 spring.datasource.hikari.maxLifetime=600000
Since the application uses Aurora MySQL, connection management is a little bit different than when using a standard MySQL instance. As explained in the Aurora connection management handbook, Aurora MySQL uses a pool of worker threads that can switch from one user session to another dynamically. Therefore, the connection pool size is set to a value that’s slightly lower than the connection limit imposed by the Aurora instance type (e.g., 45 for db.t2.small
and db.t3.small
nodes).
The spring.datasource.hikari.maxLifetime
setting instructs Hikari to retire pooled connections after 10 minutes. It’s not a very good idea to keep connections open for a very long time since a connection could be closed unexpectedly by a networking failure without the pool knowing of it.
The default isolation level is set to READ_COMMITTED
to optimize the number of gap locks held by MySQL when traversing the clustered index for bulk updates or deletes.
The auto-commit
mode is disabled, and we are going to let Hibernate know about this via the hibernate.connection.provider_disables_autocommit
setting. This way, Hibernate can acquire the database connection lazily right before executing a query or prior to flushing the Persistence Context, as opposed to the default behavior, which makes Hibernate acquire the connection right when entering a @Transactional
method.
The enable statement caching, we set the following properties:
spring.datasource.hikari.data-source-properties.useServerPrepStmts=false spring.datasource.hikari.data-source-properties.cachePrepStmts=true spring.datasource.hikari.data-source-properties.prepStmtCacheSize=500 spring.datasource.hikari.data-source-properties.prepStmtCacheSqlLimit=1024
Check out this article for a detailed explanation of each of these settings.
Spring Boot Application Properties – Hibernate Configuration
This is a list of settings that you can use if you are using Spring Data JPA, which uses Hibernate behind the scenes:
## Hibernate properties spring.jpa.hibernate.ddl-auto=none spring.jpa.show-sql=false spring.jpa.open-in-view=false spring.jpa.properties.hibernate.jdbc.time_zone=UTC spring.jpa.properties.hibernate.jdbc.batch_size=15 spring.jpa.properties.hibernate.order_inserts=true spring.jpa.properties.hibernate.order_updates=true spring.jpa.properties.hibernate.connection.provider_disables_autocommit=true spring.jpa.properties.hibernate.query.in_clause_parameter_padding=true spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch=true spring.jpa.properties.hibernate.query.plan_cache_max_size=4096 logging.level.net.ttddyy.dsproxy.listener=debug
The spring.jpa.hibernate.ddl-auto
setting is set to none
to disable the hbm2ddl schema generation tool since we are using Flyway to manage the database schema automatically.
The spring.jpa.show-sql
is set to false
to avoid Hibernate printing the SQL statements to the console. As I explained in this article, it’s better to use datasource-proxy
for this task. And that’s why we set the logging.level.net.ttddyy.dsproxy.listener
property to debug
in development mode. Of course, in the production profile, this property is set to info
.
The spring.jpa.open-in-view
property is set because we want to disable the dreadful Open-Session in View (OSIV) that’s enabled by default in Spring Boot. The OSIV anti-pattern can cause serious performance and scaling issues, so it’s better to disable it right from the very beginning of your project development.
The spring.jpa.properties.hibernate.jdbc.time_zone
property sets the default timezone to UTC
to make it easier to handle timestamps across multiple timezones. For more details about handling timezones with Spring Boot, check out this article.
To enable automatic JDBC batching, we are setting the following three properties:
spring.jpa.properties.hibernate.jdbc.batch_size=15 spring.jpa.properties.hibernate.order_inserts=true spring.jpa.properties.hibernate.order_updates=true
The first property sets the default batch size to 15
so that up to 15 sets of bind parameter values could be grouped and sent in a single database roundtrip. The next two settings are meant to increase the likelihood of batching when using cascading. Check out this article for more details about this topic.
The spring.jpa.properties.hibernate.connection.provider_disables_autocommit
property is the one that instructs Hibernate that the connection pool disables the auto-commit flag when opening database connections. Check out this article for more details about this performance tuning setting.
The spring.jpa.properties.hibernate.query.in_clause_parameter_padding
setting increases the likelihood of statement caching for IN queries as it reduces the number of possible SQL statements that could get generated while varying the IN clause parameter list. Check out this article for more details about this optimization.
The spring.jpa.properties.hibernate.query.fail_on_pagination_over_collection_fetch
property is set because we want Hibernate to throw an exception in case a pagination query uses a JOIN FETCH
directive. Check out this article for more details about this safety option.
The spring.jpa.properties.hibernate.query.plan_cache_max_size
property is set to increase the size of the Hibernate query plan cache. By using a larger cache size, we can reduce the number of JPQL and Criteria API query compilations, therefore increasing application performance. Check out this article for more details about this performance-tuning option.
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
Conclusion
Configuring the Spring Boot application properties file is a very important task because many performance tuning settings are not enabled by default.
If you’re developing a web application with Spring Boot, Hibernate, and MySQL, then the settings in this article are going to help you as much as they helped me while developing RevoGain.
