RestTemplate is dead and everybody should use WebClient now. That’s what you find everywhere so I had a look how to configure an instance of WebClient properly:
import com.fasterxml.jackson.databind.ObjectMapper
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.http.codec.ClientCodecConfigurer
import org.springframework.http.codec.json.Jackson2JsonDecoder
import org.springframework.http.codec.json.Jackson2JsonEncoder
import org.springframework.web.reactive.function.client.ExchangeStrategies
import org.springframework.web.reactive.function.client.WebClient
@Configuration
class WebClientConfig(
private val jsonMapper: ObjectMapper,
) {
@Bean
@ConditionalOnMissingBean
fun genericWebClient(): WebClient {
return WebClient.builder()
.exchangeStrategies(ExchangeStrategies
.builder()
.codecs { clientDefaultCodecsConfigurer: ClientCodecConfigurer ->
clientDefaultCodecsConfigurer.defaultCodecs()
.jackson2JsonEncoder(Jackson2JsonEncoder(jsonMapper, MediaType.APPLICATION_JSON))
clientDefaultCodecsConfigurer.defaultCodecs()
.jackson2JsonDecoder(Jackson2JsonDecoder(jsonMapper, MediaType.APPLICATION_JSON))
}.build()
)
.filter(WebClientLoggerBuilder.logRequest())
.build()
}
}
This defines a bean genericWebClient with Spring Boot configured Jackson ObjectMapper instance.
Here is also the code for the WebClientLoggerBuilder:
import org.springframework.web.reactive.function.client.ClientRequest
import org.springframework.web.reactive.function.client.ExchangeFilterFunction
import reactor.core.publisher.Mono
import java.util.function.Consumer
import org.slf4j.Logger
import org.slf4j.LoggerFactory
object WebClientLoggerBuilder {
private val log: Logger = LoggerFactory.getLogger(javaClass)
fun logRequest(): ExchangeFilterFunction {
return ExchangeFilterFunction.ofRequestProcessor { clientRequest: ClientRequest ->
if (log.isDebugEnabled) {
val sb = StringBuilder("Request: \n")
.append(clientRequest.method())
.append(" ")
.append(clientRequest.url())
.append("\n")
clientRequest.headers()
.forEach { name: String?, values: List<String?> ->
values.forEach(
Consumer { value: String? -> sb.append(name).append(": ").append(value).append("\n") })
}
log.debug(sb.toString())
}
Mono.just(clientRequest)
}
}
}
This simply logs method, path and headers of a request if debug log is enabled and added to the WebClient. Executing a blocking call that parses JSON to a list of objects is then quite easy:
fun <T> queryListWithoutBody(
path: String,
listClass: Class<T>,
params: MultiValueMap<String, String>
): List<T> {
return webClient.get()
.uri { it.path(path).queryParams(params).build() }
.retrieve()
.bodyToFlux(listClass)
.collectList()
.block() ?: emptyList()
}
With this code you’re able to replace RestTemplate without going reactive. Adding spring-boot-starter-webflux also won’t enable netty if you also have good old spring-boot-starter-web in the classpath.