Riptide
Client-side response routing for Spring
Install / Use
/learn @zalando/RiptideREADME
Riptide: A next generation HTTP client
Riptide noun, /ˈrɪp.taɪd/: strong flow of water away from the shore
Riptide is a library that implements client-side response routing. It tries to fill the gap between the HTTP protocol and Java. Riptide allows users to leverage the power of HTTP with its unique API.
- Technology stack: Based on
spring-weband uses the same foundation as Spring's RestTemplate. - Status: Actively maintained and used in production.
- Riptide is unique in the way that it doesn't abstract HTTP away, but rather embraces it!
:rotating_light: If you want to upgrade from an older version to the latest one, consult the following migration guides:
- Upgrading from 4.x to 5.x? Please refer to the Migration Guide.
- Upgrading from 3.x to 4.x? Please refer to the Migration Guide.
- Upgrading from 2.x to 3.x? Please refer to the Migration Guide.
Example
Usage typically looks like this:
http.get("/repos/{org}/{repo}/contributors", "zalando", "riptide")
.dispatch(series(),
on(SUCCESSFUL).call(listOf(User.class), users ->
users.forEach(System.out::println)));
Feel free to compare this e.g. to Feign or Retrofit.
Features
- full access to the underlying HTTP client
- resilience built into it
- isolated thread pools, connection pools and bounded queues
- transient fault detection via riptide-faults
- retries, circuit breaker, backup requests and timeouts via Failsafe integration
- non-blocking IO (optional)
- encourages the use of
- fallbacks
- content negotiation
- robust error handling
- elegant syntax
- type-safe
- asynchronous by default
- synchronous return values on demand
application/problem+jsonsupport- streaming
Origin
Most modern clients try to adapt HTTP to a single-return paradigm as shown in the following example. Even though this may be perfectly suitable for most applications, it takes away a lot of the power that comes with HTTP. It's not easy to support multiple return values, i.e. distinct happy cases. Access to response headers or manual content negotiation are also more difficult.
@GET
@Path("/repos/{org}/{repo}/contributors")
List<User> getContributors(@PathParam String org, @PathParam String repo);
Riptide tries to counter this by providing a different approach to leverage the power of HTTP. Go checkout the concept document for more details.
Dependencies
- Java 17
- Spring 7 (Spring Boot 4)
Installation
Add the following dependency to your project:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-core</artifactId>
<version>${riptide.version}</version>
</dependency>
Additional modules/artifacts of Riptide always share the same version number.
Alternatively, you can import our bill of materials...
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-bom</artifactId>
<version>${riptide.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
... which allows you to omit versions:
<dependencies>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-core</artifactId>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-failsafe</artifactId>
</dependency>
<dependency>
<groupId>org.zalando</groupId>
<artifactId>riptide-faults</artifactId>
</dependency>
</dependencies>
Configuration
Integration of your typical Spring Boot Application with Riptide, Logbook and Tracer can be greatly simplified by using the Riptide: Spring Boot Starter. Go check it out!
Http.builder()
.executor(Executors.newCachedThreadPool())
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.baseUrl("https://api.github.com")
.converter(new JacksonJsonHttpMessageConverter())
.converter(new Jaxb2RootElementHttpMessageConverter())
.plugin(new OriginalStackTracePlugin())
.build();
The following code is the bare minimum, since a request factory is required:
Http.builder()
.executor(Executors.newCachedThreadPool())
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.build();
This defaults to:
- no base URL
- same list of converters as
new RestTemplate() OriginalStackTracePlugin
Thread Pool
All off the standard Executors.new*Pool() implementations only support the queue-first style, i.e. the pool scales up to the core pool size, then fills the queue and only then will scale up to the maximum pool size.
Riptide provides a ThreadPoolExecutors.builder() which also offers a scale-first style where thread pools scale up to the maximum pool size before they queue any tasks. That usually leads to higher throughput, lower latency on the expense of having to maintain more threads.
The following table shows which combination of properties are supported
| Configuration | Supported | |--------------------------------------------|---------------------| | Without queue, fixed size¹ | :heavy_check_mark: | | Without queue, elastic size² | :heavy_check_mark: | | Bounded queue, fixed size | :heavy_check_mark: | | Bounded queue, elastic size | :heavy_check_mark: | | Unbounded queue, fixed size | :heavy_check_mark: | | Unbounded queue, elastic size | :x:³ | | Scale first, without queue, fixed size | :x:⁴ | | Scale first, without queue, elastic size | :x:⁴ | | Scale first, bounded queue, fixed size | :x:⁵ | | Scale first, bounded queue, elastic size | :heavy_check_mark:⁶ | | Scale first, unbounded queue, fixed size | :x:⁵ | | Scale first, unbounded queue, elastic size | :heavy_check_mark:⁶ |
¹ Core pool size = maximum pool size
² Core pool size < maximum pool size
³ Pool can't grow past core pool size due to unbounded queue
⁴ Scale first has no meaning without a queue
⁵ Fixed size pools are already scaled up
⁶ Elastic, but only between 0 and maximum pool size
Examples
-
Without queue, elastic size
ThreadPoolExecutors.builder() .withoutQueue() .elasticSize(5, 20) .keepAlive(1, MINUTES) .build() -
Bounded queue, fixed size
ThreadPoolExecutors.builder() .boundedQueue(20) .fixedSize(20) .keepAlive(1, MINUTES) .build() -
Scale-first, unbounded queue, elastic size
ThreadPoolExecutors.builder() .scaleFirst() .unboundedQueue() .elasticSize(20) .keepAlive(1, MINUTES) .build()
You can read more about scale-first here:
- Java Scale First ExecutorService — A myth or a reality
- How to get the ThreadPoolExecutor to increase threads to max before queueing?
In order to configure the thread pool correctly, please refer to How to set an ideal thread pool size.
Non-blocking IO
:rotating_light: While the previous versions of Riptide supported both, blocking and non-blocking request factories,
due to the removal of AsyncClientHttpRequestFactory in Spring 6, Riptide 4 only supports blocking request factories:
ApacheClientHttpRequestFactory, using the Apache HTTP Client- ~
HttpComponentsClientHttpRequestFactory~, please use the one above SimpleClientHttpRequestFactory, using [HttpURLConnection](https://docs.oracle.com/en/java/javase/17/docs

