SkillAgentSearch skills...

Riptide

Client-side response routing for Spring

Install / Use

/learn @zalando/Riptide

README

Riptide: A next generation HTTP client

Tidal wave

Stability: Active Build Status Coverage Status Code Quality Javadoc Release Maven Central License

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-web and 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:

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

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:

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

  1. Without queue, elastic size

    ThreadPoolExecutors.builder()
        .withoutQueue()
        .elasticSize(5, 20)
        .keepAlive(1, MINUTES)
        .build()
    
  2. Bounded queue, fixed size

    ThreadPoolExecutors.builder()
        .boundedQueue(20)
        .fixedSize(20)
        .keepAlive(1, MINUTES)
        .build()
    
  3. Scale-first, unbounded queue, elastic size

    ThreadPoolExecutors.builder()
        .scaleFirst()
        .unboundedQueue()
        .elasticSize(20)   
        .keepAlive(1, MINUTES)
        .build()
    

You can read more about scale-first here:

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:

View on GitHub
GitHub Stars335
CategoryDevelopment
Updated10d ago
Forks76

Languages

Java

Security Score

100/100

Audited on Mar 20, 2026

No findings