Most applications will need to deal with input and output concerns at some point. Spring Boot provides utilities and integrations with a range of technologies to help when you need IO capabilities. This section covers standard IO features such as caching and validation as well as more advanced topics such as scheduling and distributed transactions. We will also cover calling remote REST or SOAP services and sending email.
1. Caching
The Spring Framework provides support for transparently adding caching to an application.
At its core, the abstraction applies caching to methods, thus reducing the number of executions based on the information available in the cache.
The caching logic is applied transparently, without any interference to the invoker.
Spring Boot auto-configures the cache infrastructure as long as caching support is enabled by using the @EnableCaching
annotation.
Check the relevant section of the Spring Framework reference for more details. |
In a nutshell, to add caching to an operation of your service add the relevant annotation to its method, as shown in the following example:
@Component
public class MyMathService {
@Cacheable("piDecimals")
public int computePiDecimal(int precision) {
...
}
}
@Component
class MyMathService {
@Cacheable("piDecimals")
fun computePiDecimal(precision: Int): Int {
...
}
}
This example demonstrates the use of caching on a potentially costly operation.
Before invoking computePiDecimal
, the abstraction looks for an entry in the piDecimals
cache that matches the i
argument.
If an entry is found, the content in the cache is immediately returned to the caller, and the method is not invoked.
Otherwise, the method is invoked, and the cache is updated before returning the value.
You can also use the standard JSR-107 (JCache) annotations (such as @CacheResult ) transparently.
However, we strongly advise you to not mix and match the Spring Cache and JCache annotations.
|
If you do not add any specific cache library, Spring Boot auto-configures a simple provider that uses concurrent maps in memory.
When a cache is required (such as piDecimals
in the preceding example), this provider creates it for you.
The simple provider is not really recommended for production usage, but it is great for getting started and making sure that you understand the features.
When you have made up your mind about the cache provider to use, please make sure to read its documentation to figure out how to configure the caches that your application uses.
Nearly all providers require you to explicitly configure every cache that you use in the application.
Some offer a way to customize the default caches defined by the spring.cache.cache-names
property.
1.1. Supported Cache Providers
The cache abstraction does not provide an actual store and relies on abstraction materialized by the org.springframework.cache.Cache
and org.springframework.cache.CacheManager
interfaces.
If you have not defined a bean of type CacheManager
or a CacheResolver
named cacheResolver
(see CachingConfigurer
), Spring Boot tries to detect the following providers (in the indicated order):
-
JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
Additionally, Spring Boot for Apache Geode provides auto-configuration for using Apache Geode as a cache provider.
It is also possible to force a particular cache provider by setting the spring.cache.type property.
Use this property if you need to disable caching altogether in certain environments (such as tests).
|
Use the spring-boot-starter-cache “Starter” to quickly add basic caching dependencies.
The starter brings in spring-context-support .
If you add dependencies manually, you must include spring-context-support in order to use the JCache or Caffeine support.
|
If the CacheManager
is auto-configured by Spring Boot, you can further tune its configuration before it is fully initialized by exposing a bean that implements the CacheManagerCustomizer
interface.
The following example sets a flag to say that null
values should not be passed down to the underlying map:
@Configuration(proxyBeanMethods = false)
public class MyCacheManagerConfiguration {
@Bean
public CacheManagerCustomizer<ConcurrentMapCacheManager> cacheManagerCustomizer() {
return (cacheManager) -> cacheManager.setAllowNullValues(false);
}
}
@Configuration(proxyBeanMethods = false)
class MyCacheManagerConfiguration {
@Bean
fun cacheManagerCustomizer(): CacheManagerCustomizer<ConcurrentMapCacheManager> {
return CacheManagerCustomizer { cacheManager ->
cacheManager.isAllowNullValues = false
}
}
}
In the preceding example, an auto-configured ConcurrentMapCacheManager is expected.
If that is not the case (either you provided your own config or a different cache provider was auto-configured), the customizer is not invoked at all.
You can have as many customizers as you want, and you can also order them by using @Order or Ordered .
|
1.1.1. Generic
Generic caching is used if the context defines at least one org.springframework.cache.Cache
bean.
A CacheManager
wrapping all beans of that type is created.
1.1.2. JCache (JSR-107)
JCache is bootstrapped through the presence of a javax.cache.spi.CachingProvider
on the classpath (that is, a JSR-107 compliant caching library exists on the classpath), and the JCacheCacheManager
is provided by the spring-boot-starter-cache
“Starter”.
Various compliant libraries are available, and Spring Boot provides dependency management for Ehcache 3, Hazelcast, and Infinispan.
Any other compliant library can be added as well.
It might happen that more than one provider is present, in which case the provider must be explicitly specified. Even if the JSR-107 standard does not enforce a standardized way to define the location of the configuration file, Spring Boot does its best to accommodate setting a cache with implementation details, as shown in the following example:
# Only necessary if more than one provider is present
spring.cache.jcache.provider=com.example.MyCachingProvider
spring.cache.jcache.config=classpath:example.xml
# Only necessary if more than one provider is present
spring:
cache:
jcache:
provider: "com.example.MyCachingProvider"
config: "classpath:example.xml"
When a cache library offers both a native implementation and JSR-107 support, Spring Boot prefers the JSR-107 support, so that the same features are available if you switch to a different JSR-107 implementation. |
Spring Boot has general support for Hazelcast.
If a single HazelcastInstance is available, it is automatically reused for the CacheManager as well, unless the spring.cache.jcache.config property is specified.
|
There are two ways to customize the underlying javax.cache.cacheManager
:
-
Caches can be created on startup by setting the
spring.cache.cache-names
property. If a customjavax.cache.configuration.Configuration
bean is defined, it is used to customize them. -
org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer
beans are invoked with the reference of theCacheManager
for full customization.
If a standard javax.cache.CacheManager bean is defined, it is wrapped automatically in an org.springframework.cache.CacheManager implementation that the abstraction expects.
No further customization is applied to it.
|
1.1.3. Hazelcast
Spring Boot has general support for Hazelcast.
If a HazelcastInstance
has been auto-configured, it is automatically wrapped in a CacheManager
.
1.1.4. Infinispan
Infinispan has no default configuration file location, so it must be specified explicitly. Otherwise, the default bootstrap is used.
spring.cache.infinispan.config=infinispan.xml
spring:
cache:
infinispan:
config: "infinispan.xml"
Caches can be created on startup by setting the spring.cache.cache-names
property.
If a custom ConfigurationBuilder
bean is defined, it is used to customize the caches.
To be compatible with Spring Boot’s Jakarta EE 9 baseline, Infinispan’s -jakarta
modules must be used.
For every module with a -jakarta
variant, the variant must be used in place of the standard module.
For example, infinispan-core-jakarta
and infinispan-commons-jakarta
must be used in place of infinispan-core
and infinispan-commons
respectively.
The support of Infinispan in Spring Boot is restricted to the embedded mode and is quite basic. If you want more options, you should use the official Infinispan Spring Boot starter instead. See Infinispan’s documentation for more details. |
1.1.5. Couchbase
If Spring Data Couchbase is available and Couchbase is configured, a CouchbaseCacheManager
is auto-configured.
It is possible to create additional caches on startup by setting the spring.cache.cache-names
property and cache defaults can be configured by using spring.cache.couchbase.*
properties.
For instance, the following configuration creates cache1
and cache2
caches with an entry expiration of 10 minutes:
spring.cache.cache-names=cache1,cache2
spring.cache.couchbase.expiration=10m
spring:
cache:
cache-names: "cache1,cache2"
couchbase:
expiration: "10m"
If you need more control over the configuration, consider registering a CouchbaseCacheManagerBuilderCustomizer
bean.
The following example shows a customizer that configures a specific entry expiration for cache1
and cache2
:
@Configuration(proxyBeanMethods = false)
public class MyCouchbaseCacheManagerConfiguration {
@Bean
public CouchbaseCacheManagerBuilderCustomizer myCouchbaseCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration("cache1", CouchbaseCacheConfiguration
.defaultCacheConfig().entryExpiry(Duration.ofSeconds(10)))
.withCacheConfiguration("cache2", CouchbaseCacheConfiguration
.defaultCacheConfig().entryExpiry(Duration.ofMinutes(1)));
}
}
@Configuration(proxyBeanMethods = false)
class MyCouchbaseCacheManagerConfiguration {
@Bean
fun myCouchbaseCacheManagerBuilderCustomizer(): CouchbaseCacheManagerBuilderCustomizer {
return CouchbaseCacheManagerBuilderCustomizer { builder ->
builder
.withCacheConfiguration(
"cache1", CouchbaseCacheConfiguration
.defaultCacheConfig().entryExpiry(Duration.ofSeconds(10))
)
.withCacheConfiguration(
"cache2", CouchbaseCacheConfiguration
.defaultCacheConfig().entryExpiry(Duration.ofMinutes(1))
)
}
}
}
1.1.6. Redis
If Redis is available and configured, a RedisCacheManager
is auto-configured.
It is possible to create additional caches on startup by setting the spring.cache.cache-names
property and cache defaults can be configured by using spring.cache.redis.*
properties.
For instance, the following configuration creates cache1
and cache2
caches with a time to live of 10 minutes:
spring.cache.cache-names=cache1,cache2
spring.cache.redis.time-to-live=10m
spring:
cache:
cache-names: "cache1,cache2"
redis:
time-to-live: "10m"
By default, a key prefix is added so that, if two separate caches use the same key, Redis does not have overlapping keys and cannot return invalid values.
We strongly recommend keeping this setting enabled if you create your own RedisCacheManager .
|
You can take full control of the default configuration by adding a RedisCacheConfiguration @Bean of your own.
This can be useful if you need to customize the default serialization strategy.
|
If you need more control over the configuration, consider registering a RedisCacheManagerBuilderCustomizer
bean.
The following example shows a customizer that configures a specific time to live for cache1
and cache2
:
@Configuration(proxyBeanMethods = false)
public class MyRedisCacheManagerConfiguration {
@Bean
public RedisCacheManagerBuilderCustomizer myRedisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration("cache1", RedisCacheConfiguration
.defaultCacheConfig().entryTtl(Duration.ofSeconds(10)))
.withCacheConfiguration("cache2", RedisCacheConfiguration
.defaultCacheConfig().entryTtl(Duration.ofMinutes(1)));
}
}
@Configuration(proxyBeanMethods = false)
class MyRedisCacheManagerConfiguration {
@Bean
fun myRedisCacheManagerBuilderCustomizer(): RedisCacheManagerBuilderCustomizer {
return RedisCacheManagerBuilderCustomizer { builder ->
builder
.withCacheConfiguration(
"cache1", RedisCacheConfiguration
.defaultCacheConfig().entryTtl(Duration.ofSeconds(10))
)
.withCacheConfiguration(
"cache2", RedisCacheConfiguration
.defaultCacheConfig().entryTtl(Duration.ofMinutes(1))
)
}
}
}
1.1.7. Caffeine
Caffeine is a Java 8 rewrite of Guava’s cache that supersedes support for Guava.
If Caffeine is present, a CaffeineCacheManager
(provided by the spring-boot-starter-cache
“Starter”) is auto-configured.
Caches can be created on startup by setting the spring.cache.cache-names
property and can be customized by one of the following (in the indicated order):
-
A cache spec defined by
spring.cache.caffeine.spec
-
A
com.github.benmanes.caffeine.cache.CaffeineSpec
bean is defined -
A
com.github.benmanes.caffeine.cache.Caffeine
bean is defined
For instance, the following configuration creates cache1
and cache2
caches with a maximum size of 500 and a time to live of 10 minutes
spring.cache.cache-names=cache1,cache2
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s
spring:
cache:
cache-names: "cache1,cache2"
caffeine:
spec: "maximumSize=500,expireAfterAccess=600s"
If a com.github.benmanes.caffeine.cache.CacheLoader
bean is defined, it is automatically associated to the CaffeineCacheManager
.
Since the CacheLoader
is going to be associated with all caches managed by the cache manager, it must be defined as CacheLoader<Object, Object>
.
The auto-configuration ignores any other generic type.
1.1.8. Cache2k
Cache2k is an in-memory cache.
If the Cache2k spring integration is present, a SpringCache2kCacheManager
is auto-configured.
Caches can be created on startup by setting the spring.cache.cache-names
property.
Cache defaults can be customized using a Cache2kBuilderCustomizer
bean.
The following example shows a customizer that configures the capacity of the cache to 200 entries, with an expiration of 5 minutes:
@Configuration(proxyBeanMethods = false)
public class MyCache2kDefaultsConfiguration {
@Bean
public Cache2kBuilderCustomizer myCache2kDefaultsCustomizer() {
return (builder) -> builder.entryCapacity(200)
.expireAfterWrite(5, TimeUnit.MINUTES);
}
}
@Configuration(proxyBeanMethods = false)
class MyCache2kDefaultsConfiguration {
@Bean
fun myCache2kDefaultsCustomizer(): Cache2kBuilderCustomizer {
return Cache2kBuilderCustomizer { builder ->
builder.entryCapacity(200)
.expireAfterWrite(5, TimeUnit.MINUTES)
}
}
}
1.1.9. Simple
If none of the other providers can be found, a simple implementation using a ConcurrentHashMap
as the cache store is configured.
This is the default if no caching library is present in your application.
By default, caches are created as needed, but you can restrict the list of available caches by setting the cache-names
property.
For instance, if you want only cache1
and cache2
caches, set the cache-names
property as follows:
spring.cache.cache-names=cache1,cache2
spring:
cache:
cache-names: "cache1,cache2"
If you do so and your application uses a cache not listed, then it fails at runtime when the cache is needed, but not on startup. This is similar to the way the "real" cache providers behave if you use an undeclared cache.
1.1.10. None
When @EnableCaching
is present in your configuration, a suitable cache configuration is expected as well.
If you need to disable caching altogether in certain environments, force the cache type to none
to use a no-op implementation, as shown in the following example:
spring.cache.type=none
spring:
cache:
type: "none"
2. Hazelcast
If Hazelcast is on the classpath and a suitable configuration is found, Spring Boot auto-configures a HazelcastInstance
that you can inject in your application.
Spring Boot first attempts to create a client by checking the following configuration options:
-
The presence of a
com.hazelcast.client.config.ClientConfig
bean. -
A configuration file defined by the
spring.hazelcast.config
property. -
The presence of the
hazelcast.client.config
system property. -
A
hazelcast-client.xml
in the working directory or at the root of the classpath. -
A
hazelcast-client.yaml
(orhazelcast-client.yml
) in the working directory or at the root of the classpath.
If a client can not be created, Spring Boot attempts to configure an embedded server.
If you define a com.hazelcast.config.Config
bean, Spring Boot uses that.
If your configuration defines an instance name, Spring Boot tries to locate an existing instance rather than creating a new one.
You could also specify the Hazelcast configuration file to use through configuration, as shown in the following example:
spring.hazelcast.config=classpath:config/my-hazelcast.xml
spring:
hazelcast:
config: "classpath:config/my-hazelcast.xml"
Otherwise, Spring Boot tries to find the Hazelcast configuration from the default locations: hazelcast.xml
in the working directory or at the root of the classpath, or a .yaml
/.yml
counterpart in the same locations.
We also check if the hazelcast.config
system property is set.
See the Hazelcast documentation for more details.
By default, @SpringAware on Hazelcast components is supported.
The ManagementContext can be overridden by declaring a HazelcastConfigCustomizer bean with an @Order higher than zero.
|
Spring Boot also has explicit caching support for Hazelcast.
If caching is enabled, the HazelcastInstance is automatically wrapped in a CacheManager implementation.
|
3. Quartz Scheduler
Spring Boot offers several conveniences for working with the Quartz scheduler, including the spring-boot-starter-quartz
“Starter”.
If Quartz is available, a Scheduler
is auto-configured (through the SchedulerFactoryBean
abstraction).
Beans of the following types are automatically picked up and associated with the Scheduler
:
-
JobDetail
: defines a particular Job.JobDetail
instances can be built with theJobBuilder
API. -
Calendar
. -
Trigger
: defines when a particular job is triggered.
By default, an in-memory JobStore
is used.
However, it is possible to configure a JDBC-based store if a DataSource
bean is available in your application and if the spring.quartz.job-store-type
property is configured accordingly, as shown in the following example:
spring.quartz.job-store-type=jdbc
spring:
quartz:
job-store-type: "jdbc"
When the JDBC store is used, the schema can be initialized on startup, as shown in the following example:
spring.quartz.jdbc.initialize-schema=always
spring:
quartz:
jdbc:
initialize-schema: "always"
By default, the database is detected and initialized by using the standard scripts provided with the Quartz library.
These scripts drop existing tables, deleting all triggers on every restart.
It is also possible to provide a custom script by setting the spring.quartz.jdbc.schema property.
|
To have Quartz use a DataSource
other than the application’s main DataSource
, declare a DataSource
bean, annotating its @Bean
method with @QuartzDataSource
.
Doing so ensures that the Quartz-specific DataSource
is used by both the SchedulerFactoryBean
and for schema initialization.
Similarly, to have Quartz use a TransactionManager
other than the application’s main TransactionManager
declare a TransactionManager
bean, annotating its @Bean
method with @QuartzTransactionManager
.
By default, jobs created by configuration will not overwrite already registered jobs that have been read from a persistent job store.
To enable overwriting existing job definitions set the spring.quartz.overwrite-existing-jobs
property.
Quartz Scheduler configuration can be customized using spring.quartz
properties and SchedulerFactoryBeanCustomizer
beans, which allow programmatic SchedulerFactoryBean
customization.
Advanced Quartz configuration properties can be customized using spring.quartz.properties.*
.
In particular, an Executor bean is not associated with the scheduler as Quartz offers a way to configure the scheduler through spring.quartz.properties .
If you need to customize the task executor, consider implementing SchedulerFactoryBeanCustomizer .
|
Jobs can define setters to inject data map properties. Regular beans can also be injected in a similar manner, as shown in the following example:
public class MySampleJob extends QuartzJobBean {
// Inject "MyService" bean
public void setMyService(MyService myService) {
this.myService = myService;
}
// Inject the "name" job data property
public void setName(String name) {
this.name = name;
}
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
this.myService.someMethod(context.getFireTime(), this.name);
}
}
class MySampleJob : QuartzJobBean() {
// Inject "MyService" bean
fun setMyService(myService: MyService?) {
this.myService = myService
}
// Inject the "name" job data property
fun setName(name: String?) {
this.name = name
}
override fun executeInternal(context: JobExecutionContext) {
myService!!.someMethod(context.fireTime, name)
}
}
4. Sending Email
The Spring Framework provides an abstraction for sending email by using the JavaMailSender
interface, and Spring Boot provides auto-configuration for it as well as a starter module.
See the reference documentation for a detailed explanation of how you can use JavaMailSender .
|
If spring.mail.host
and the relevant libraries (as defined by spring-boot-starter-mail
) are available, a default JavaMailSender
is created if none exists.
The sender can be further customized by configuration items from the spring.mail
namespace.
See MailProperties
for more details.
In particular, certain default timeout values are infinite, and you may want to change that to avoid having a thread blocked by an unresponsive mail server, as shown in the following example:
spring.mail.properties[mail.smtp.connectiontimeout]=5000
spring.mail.properties[mail.smtp.timeout]=3000
spring.mail.properties[mail.smtp.writetimeout]=5000
spring:
mail:
properties:
"[mail.smtp.connectiontimeout]": 5000
"[mail.smtp.timeout]": 3000
"[mail.smtp.writetimeout]": 5000
It is also possible to configure a JavaMailSender
with an existing Session
from JNDI:
spring.mail.jndi-name=mail/Session
spring:
mail:
jndi-name: "mail/Session"
When a jndi-name
is set, it takes precedence over all other Session-related settings.
5. Validation
The method validation feature supported by Bean Validation 1.1 is automatically enabled as long as a JSR-303 implementation (such as Hibernate validator) is on the classpath.
This lets bean methods be annotated with javax.validation
constraints on their parameters and/or on their return value.
Target classes with such annotated methods need to be annotated with the @Validated
annotation at the type level for their methods to be searched for inline constraint annotations.
For instance, the following service triggers the validation of the first argument, making sure its size is between 8 and 10:
@Service
@Validated
public class MyBean {
public Archive findByCodeAndAuthor(@Size(min = 8, max = 10) String code, Author author) {
return ...
}
}
@Service
@Validated
class MyBean {
fun findByCodeAndAuthor(code: @Size(min = 8, max = 10) String?, author: Author?): Archive? {
return null
}
}
The application’s MessageSource
is used when resolving {parameters}
in constraint messages.
This allows you to use your application’s messages.properties
files for Bean Validation messages.
Once the parameters have been resolved, message interpolation is completed using Bean Validation’s default interpolator.
To customize the Configuration
used to build the ValidatorFactory
, define a ValidationConfigurationCustomizer
bean.
When multiple customizer beans are defined, they are called in order based on their @Order
annotation or Ordered
implementation.
6. Calling REST Services
If your application calls remote REST services, Spring Boot makes that very convenient using a RestTemplate
or a WebClient
.
6.1. RestTemplate
If you need to call remote REST services from your application, you can use the Spring Framework’s RestTemplate
class.
Since RestTemplate
instances often need to be customized before being used, Spring Boot does not provide any single auto-configured RestTemplate
bean.
It does, however, auto-configure a RestTemplateBuilder
, which can be used to create RestTemplate
instances when needed.
The auto-configured RestTemplateBuilder
ensures that sensible HttpMessageConverters
are applied to RestTemplate
instances.
The following code shows a typical example:
@Service
public class MyService {
private final RestTemplate restTemplate;
public MyService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
public Details someRestCall(String name) {
return this.restTemplate.getForObject("/{name}/details", Details.class, name);
}
}
@Service
class MyService(restTemplateBuilder: RestTemplateBuilder) {
private val restTemplate: RestTemplate
init {
restTemplate = restTemplateBuilder.build()
}
fun someRestCall(name: String): Details {
return restTemplate.getForObject(
"/{name}/details",
Details::class.java, name
)!!
}
}
RestTemplateBuilder includes a number of useful methods that can be used to quickly configure a RestTemplate .
For example, to add BASIC auth support, you can use builder.basicAuthentication("user", "password").build() .
|
6.1.1. RestTemplate Customization
There are three main approaches to RestTemplate
customization, depending on how broadly you want the customizations to apply.
To make the scope of any customizations as narrow as possible, inject the auto-configured RestTemplateBuilder
and then call its methods as required.
Each method call returns a new RestTemplateBuilder
instance, so the customizations only affect this use of the builder.
To make an application-wide, additive customization, use a RestTemplateCustomizer
bean.
All such beans are automatically registered with the auto-configured RestTemplateBuilder
and are applied to any templates that are built with it.
The following example shows a customizer that configures the use of a proxy for all hosts except 192.168.0.5
:
public class MyRestTemplateCustomizer implements RestTemplateCustomizer {
@Override
public void customize(RestTemplate restTemplate) {
HttpRoutePlanner routePlanner = new CustomRoutePlanner(new HttpHost("proxy.example.com"));
HttpClient httpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient));
}
static class CustomRoutePlanner extends DefaultProxyRoutePlanner {
CustomRoutePlanner(HttpHost proxy) {
super(proxy);
}
@Override
protected HttpHost determineProxy(HttpHost target, HttpContext context) throws HttpException {
if (target.getHostName().equals("192.168.0.5")) {
return null;
}
return super.determineProxy(target, context);
}
}
}
class MyRestTemplateCustomizer : RestTemplateCustomizer {
override fun customize(restTemplate: RestTemplate) {
val routePlanner: HttpRoutePlanner = CustomRoutePlanner(HttpHost("proxy.example.com"))
val httpClient: HttpClient = HttpClientBuilder.create().setRoutePlanner(routePlanner).build()
restTemplate.requestFactory = HttpComponentsClientHttpRequestFactory(httpClient)
}
internal class CustomRoutePlanner(proxy: HttpHost?) : DefaultProxyRoutePlanner(proxy) {
@Throws(HttpException::class)
public override fun determineProxy(target: HttpHost, context: HttpContext): HttpHost? {
if (target.hostName == "192.168.0.5") {
return null
}
return super.determineProxy(target, context)
}
}
}
Finally, you can define your own RestTemplateBuilder
bean.
Doing so will replace the auto-configured builder.
If you want any RestTemplateCustomizer
beans to be applied to your custom builder, as the auto-configuration would have done, configure it using a RestTemplateBuilderConfigurer
.
The following example exposes a RestTemplateBuilder
that matches what Spring Boot’s auto-configuration would have done, except that custom connect and read timeouts are also specified:
@Configuration(proxyBeanMethods = false)
public class MyRestTemplateBuilderConfiguration {
@Bean
public RestTemplateBuilder restTemplateBuilder(RestTemplateBuilderConfigurer configurer) {
return configurer.configure(new RestTemplateBuilder()).setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(2));
}
}
@Configuration(proxyBeanMethods = false)
class MyRestTemplateBuilderConfiguration {
@Bean
fun restTemplateBuilder(configurer: RestTemplateBuilderConfigurer): RestTemplateBuilder {
return configurer.configure(RestTemplateBuilder()).setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(2))
}
}
The most extreme (and rarely used) option is to create your own RestTemplateBuilder
bean without using a configurer.
In addition to replacing the auto-configured builder, this also prevents any RestTemplateCustomizer
beans from being used.
6.2. WebClient
If you have Spring WebFlux on your classpath, you can also choose to use WebClient
to call remote REST services.
Compared to RestTemplate
, this client has a more functional feel and is fully reactive.
You can learn more about the WebClient
in the dedicated section in the Spring Framework docs.
Spring Boot creates and pre-configures a WebClient.Builder
for you.
It is strongly advised to inject it in your components and use it to create WebClient
instances.
Spring Boot is configuring that builder to share HTTP resources, reflect codecs setup in the same fashion as the server ones (see WebFlux HTTP codecs auto-configuration), and more.
The following code shows a typical example:
@Service
public class MyService {
private final WebClient webClient;
public MyService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("https://example.org").build();
}
public Mono<Details> someRestCall(String name) {
return this.webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(Details.class);
}
}
@Service
class MyService(webClientBuilder: WebClient.Builder) {
private val webClient: WebClient
init {
webClient = webClientBuilder.baseUrl("https://example.org").build()
}
fun someRestCall(name: String?): Mono<Relationship.Details> {
return webClient.get().uri("/{name}/details", name).retrieve().bodyToMono(
Relationship.Details::class.java
)
}
}
6.2.1. WebClient Runtime
Spring Boot will auto-detect which ClientHttpConnector
to use to drive WebClient
, depending on the libraries available on the application classpath.
For now, Reactor Netty, Jetty RS client, Apache HttpClient, and the JDK’s HttpClient are supported.
The spring-boot-starter-webflux
starter depends on io.projectreactor.netty:reactor-netty
by default, which brings both server and client implementations.
If you choose to use Jetty as a reactive server instead, you should add a dependency on the Jetty Reactive HTTP client library, org.eclipse.jetty:jetty-reactive-httpclient
.
Using the same technology for server and client has its advantages, as it will automatically share HTTP resources between client and server.
Developers can override the resource configuration for Jetty and Reactor Netty by providing a custom ReactorResourceFactory
or JettyResourceFactory
bean - this will be applied to both clients and servers.
If you wish to override that choice for the client, you can define your own ClientHttpConnector
bean and have full control over the client configuration.
You can learn more about the WebClient
configuration options in the Spring Framework reference documentation.
6.2.2. WebClient Customization
There are three main approaches to WebClient
customization, depending on how broadly you want the customizations to apply.
To make the scope of any customizations as narrow as possible, inject the auto-configured WebClient.Builder
and then call its methods as required.
WebClient.Builder
instances are stateful: Any change on the builder is reflected in all clients subsequently created with it.
If you want to create several clients with the same builder, you can also consider cloning the builder with WebClient.Builder other = builder.clone();
.
To make an application-wide, additive customization to all WebClient.Builder
instances, you can declare WebClientCustomizer
beans and change the WebClient.Builder
locally at the point of injection.
Finally, you can fall back to the original API and use WebClient.create()
.
In that case, no auto-configuration or WebClientCustomizer
is applied.
7. Web Services
Spring Boot provides Web Services auto-configuration so that all you must do is define your Endpoints
.
The Spring Web Services features can be easily accessed with the spring-boot-starter-webservices
module.
SimpleWsdl11Definition
and SimpleXsdSchema
beans can be automatically created for your WSDLs and XSDs respectively.
To do so, configure their location, as shown in the following example:
spring.webservices.wsdl-locations=classpath:/wsdl
spring:
webservices:
wsdl-locations: "classpath:/wsdl"
7.1. Calling Web Services with WebServiceTemplate
If you need to call remote Web services from your application, you can use the WebServiceTemplate
class.
Since WebServiceTemplate
instances often need to be customized before being used, Spring Boot does not provide any single auto-configured WebServiceTemplate
bean.
It does, however, auto-configure a WebServiceTemplateBuilder
, which can be used to create WebServiceTemplate
instances when needed.
The following code shows a typical example:
@Service
public class MyService {
private final WebServiceTemplate webServiceTemplate;
public MyService(WebServiceTemplateBuilder webServiceTemplateBuilder) {
this.webServiceTemplate = webServiceTemplateBuilder.build();
}
public SomeResponse someWsCall(SomeRequest detailsReq) {
return (SomeResponse) this.webServiceTemplate.marshalSendAndReceive(detailsReq,
new SoapActionCallback("https://ws.example.com/action"));
}
}
@Service
class MyService(webServiceTemplateBuilder: WebServiceTemplateBuilder) {
private val webServiceTemplate: WebServiceTemplate
init {
webServiceTemplate = webServiceTemplateBuilder.build()
}
fun someWsCall(detailsReq: SomeRequest?): SomeResponse {
return webServiceTemplate.marshalSendAndReceive(
detailsReq,
SoapActionCallback("https://ws.example.com/action")
) as SomeResponse
}
}
By default, WebServiceTemplateBuilder
detects a suitable HTTP-based WebServiceMessageSender
using the available HTTP client libraries on the classpath.
You can also customize read and connection timeouts as follows:
@Configuration(proxyBeanMethods = false)
public class MyWebServiceTemplateConfiguration {
@Bean
public WebServiceTemplate webServiceTemplate(WebServiceTemplateBuilder builder) {
WebServiceMessageSender sender = new HttpWebServiceMessageSenderBuilder()
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(2))
.build();
return builder.messageSenders(sender).build();
}
}
@Configuration(proxyBeanMethods = false)
class MyWebServiceTemplateConfiguration {
@Bean
fun webServiceTemplate(builder: WebServiceTemplateBuilder): WebServiceTemplate {
val sender = HttpWebServiceMessageSenderBuilder()
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(2))
.build()
return builder.messageSenders(sender).build()
}
}
8. Distributed Transactions With JTA
Spring Boot supports distributed JTA transactions across multiple XA resources by using a transaction manager retrieved from JNDI.
When a JTA environment is detected, Spring’s JtaTransactionManager
is used to manage transactions.
Auto-configured JMS, DataSource, and JPA beans are upgraded to support XA transactions.
You can use standard Spring idioms, such as @Transactional
, to participate in a distributed transaction.
If you are within a JTA environment and still want to use local transactions, you can set the spring.jta.enabled
property to false
to disable the JTA auto-configuration.
8.1. Using a Jakarta EE Managed Transaction Manager
If you package your Spring Boot application as a war
or ear
file and deploy it to a Jakarta EE application server, you can use your application server’s built-in transaction manager.
Spring Boot tries to auto-configure a transaction manager by looking at common JNDI locations (java:comp/UserTransaction
, java:comp/TransactionManager
, and so on).
When using a transaction service provided by your application server, you generally also want to ensure that all resources are managed by the server and exposed over JNDI.
Spring Boot tries to auto-configure JMS by looking for a ConnectionFactory
at the JNDI path (java:/JmsXA
or java:/XAConnectionFactory
), and you can use the spring.datasource.jndi-name
property to configure your DataSource
.
8.2. Mixing XA and Non-XA JMS Connections
When using JTA, the primary JMS ConnectionFactory
bean is XA-aware and participates in distributed transactions.
You can inject into your bean without needing to use any @Qualifier
:
public MyBean(ConnectionFactory connectionFactory) {
// ...
}
In some situations, you might want to process certain JMS messages by using a non-XA ConnectionFactory
.
For example, your JMS processing logic might take longer than the XA timeout.
If you want to use a non-XA ConnectionFactory
, you can the nonXaJmsConnectionFactory
bean:
public MyBean(@Qualifier("nonXaJmsConnectionFactory") ConnectionFactory connectionFactory) {
// ...
}
For consistency, the jmsConnectionFactory
bean is also provided by using the bean alias xaJmsConnectionFactory
:
public MyBean(@Qualifier("xaJmsConnectionFactory") ConnectionFactory connectionFactory) {
// ...
}
8.3. Supporting an Embedded Transaction Manager
The XAConnectionFactoryWrapper
and XADataSourceWrapper
interfaces can be used to support embedded transaction managers.
The interfaces are responsible for wrapping XAConnectionFactory
and XADataSource
beans and exposing them as regular ConnectionFactory
and DataSource
beans, which transparently enroll in the distributed transaction.
DataSource and JMS auto-configuration use JTA variants, provided you have a JtaTransactionManager
bean and appropriate XA wrapper beans registered within your ApplicationContext
.
9. What to Read Next
You should now have a good understanding of Spring Boot’s core features and the various technologies that Spring Boot provides support for via auto-configuration.
The next few sections go into detail about deploying applications to cloud platforms. You can read about building container images in the next section or skip to the production-ready features section.