How to use a custom class as @PathVariable in Feign 👨‍🏫
Tags: java, architecture, programming, api
Published: 2021-05-20 23:38:23

---
title: How to use a custom class as @PathVariable in Feign 👨‍🏫
published: true
date: 2021-05-06 23:15:00 UTC
tags: java, architecture, programming, api
canonical_url: https://www.benjaminrancourt.ca/how-to-use-a-custom-class-as-pathvariable-in-feign
cover_image: https://www.benjaminrancourt.ca/images/how-to-use-a-custom-class-as-pathvariable-in-feign.jpg
---
Do you know [**Feign**](https://github.com/OpenFeign/feign)? It is a Java library that allows you to write a REST client with minimal code. In my current work project, we are currently using it and as we had no experience with it, we are continually improving our knowledge on this library.

We recently encountered a particular use case, that although we could not find an example on the Internet, we were able to find a working solution that you might be interested in. 🥰

## Context

We are currently using an **external REST API** which seems a bit special: for their GET endpoints, we are able to **filter** the returned ressources with their API, but we cannot pass these filters as normal GET parameters. We have to pass them directly in the URL with a special syntax! 😲

Let's see an example. If we want to filter the `/activities` endpoint to get the activity with ID 65, we need to call the `/activities/id;65` endpoint. A little weird, right? 🤔

### OR filter

If we want to get two separate activities that have the ID 65 **OR** 76, the syntax is similar. We must add `;76` to the previous endpoint: `/activities/id;65;76`.

### AND filter

Unfortunately, the filters can be more complicated. If we want to have the activities for 2021 (therefore from January 1 to December 31), we must have an **AND** filter. And what do you think the syntax looks like? 😉

The URL would be `/activities/startDate;012F%012F%2021/endDate;122F%312F%2021`. I am pretty sure you did not guess it, right? 🤣 The `2F%` is the encoded version of a slash (`/`) character. And yes, the dates must be in the `MM/DD/YYYY` format... 😓

## Toward the solution

Since we did not want to manually create the filters for all endpoints before calling them, we started looking for a better solution. So we start with a single `@GetMapping` endpoint:

```java
  @GetMapping("/activities/{filters}")
  List<Activity> getActivities(
      @PathVariable(value = "filters") final String filters
  );
```
<figcaption>An excerpt of our Feign client, with a <strong>String</strong> as a <code>@PathVariable</code></figcaption>

But although the API returns results when we call it from our code, we found that the filters were not applied and the API returned all activities instead of an error... 🤯

### Encoding

It turns out that Feign automatically encodes the URL path variable, so the final URI looks like `/activities/startDate%3B04%252F01%252F2021` instead of `/activities/startDate;012F%012F%2021` that their application was expecting (I removed the `endDate` filter for clarity).

Thus, semicolons (`;`) and forward slashes (`/`) were encoded, as they are generally not expected in a path variable. Fortunately, we quickly found [this answer from StackOverflow](https://stackoverflow.com/a/61901509) that steers us on the concept of **RequestInterceptor**. We were able to decode the encoded values with the code below. 😌

```java
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;

/**
 * Interceptor to alter the URL used for the external API. The API uses characters that are encoded by Feign, so
 * this interceptor decode them back to their original values.
 *
 * Inspired from https://stackoverflow.com/a/61901509
 */
@Component
public class PathEncodingExclusionInterceptor implements RequestInterceptor {
  private static final String SEMI_COLON = "%3B";
  private static final String PERCENTAGE = "%25";

  @Override
  public void apply(final RequestTemplate template) {
    final String path = template.path();

    // Make sure to decode the URL only one time, as the uri function can takes only relative URLs
    if (path.contains(SEMI_COLON) || path.contains(PERCENTAGE)) {
      // SOURCE : startDate ; 04 % 2F 01 % 2F 2021 /
      // BEFORE : startDate %3B 04 %25 2F 01 %25 2F 2021 /
      // AFTER : startDate ; 04 % 2F 01 % 2F 2021 /
      template.uri(path.replaceAll(SEMI_COLON, ";").replaceAll(PERCENTAGE, "%"));
    }
  }
}

```
<figcaption>Our special <em>RequestInterceptor </em>to decode some characters</figcaption>

Now that this issue is resolved, let's get back to creating filters.

## Final solution

### Use a custom class as @PathVariable

Our original solution was to create a custom builder class that would build a **String** based on certain values passed as parameters.

But going through the code created by my colleague, I wonder if we could just pass a **custom class** to Feign as a `@PathVariable` instead. So we quickly test the following code, where `FilterField` is our custom class:

```java
  @GetMapping("/activities/{filters}")
  List<Activity> getActivities(
      @PathVariable(value = "filters") final FilterField filters
  );
```
<figcaption>We replace the type of the <code>@PathVariable</code> from <code>String</code> to <code>FilterField</code></figcaption>

Let's see the final URL that Feign calls in the output:

```
/activities/ca.usherbrooke.filter.FilterField%407ab4ae59
```
<figcaption>The URL, with the class name and something that looks like a hash code...</figcaption>
### Overriding the toString function

Interesting, isn't it? This looks like the default implementation of an object's **toString** function. And what happens when we override it into our class?

```java
package ca.usherbrooke.filter;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class FilterField {
  private final String field;
  private final List<String> values;
  
    @Override
    public String toString() {
      final String joinedValues = String.join(";", values);
      return String.format("%s;%s", field, joinedValues);
    }
}

```
<figcaption>An excerpt of our <code>FilterField</code> class</figcaption>

You guessed it, our URL looks like exactly as we want! 🙂

```
/activities/startDate;012F%012F%2021
```
<figcaption>Much better, right?</figcaption>
### Using a List

But since we want to have the possibility to use a list of `FilterField`, let's update our client t0 allow it:

```java
  @GetMapping("/activities/{filters}")
  List<Activity> getActivities(
      @PathVariable(value = "filters") final List<FilterField> filters
  );
```
<figcaption>We replace the type of the <code>@PathVariable</code> from <code>FilterField</code> to <code>List&lt;FilterField&gt;</code></figcaption>

```
/activities/startDate;012F%012F%2021,endDate;122F%312F%2021
```
<figcaption>Meh, we have a comma between our filters...</figcaption>

This is almost correct, the separator between the filters should be a slash (`/`) instead of a comma (`,`)! Guess how we solved this problem? By creating a new class! 😄

### Creation of a FilterFieldList

First, let's replace our code in our Feign client:

```java
  @GetMapping("/activities/{filters}")
  List<Activity> getActivities(
      @PathVariable(value = "filters") final FilterFieldList filters
  );
```
<figcaption>We finally replace the type of the <code>@PathVariable</code> from <code>List&lt;FilterField&gt;</code> to <code>FilterFieldList</code></figcaption>

And now, the implementation of our `FilterFieldList` class:

```java
package ca.usherbrooke.filter;

import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Collectors;

public class FilterFieldList {
  private final List<FilterField> fields;

  public FilterFieldList(final FilterField... filterFields) {
    fields = Arrays.asList(filterFields);
  }

  @Override
  public String toString() {
    final List<String> strings =
        fields.stream().map(FilterField::toString).collect(Collectors.toList());
    return String.join("/", strings);
  }
}

```
<figcaption>Implementation code of the <code>FilterFieldList</code> class</figcaption>

With this final class, we now have a complete working solution for the weird external API that we are using! 🥳

```
/activities/startDate;012F%012F%2021/endDate;122F%312F%2021
```
<figcaption>The final URL, exactly as we want, but without using a <code>String @PathVariable</code>!</figcaption>

We initially try to extend `List<FilterField>` with the overridden function, but somehow, Feign did not call our `toString` function... Maybe Feign is doing something different if the custom class is an instance of `List`? 🤔

## Conclusion

So, in this post, you learned about two Feign concepts: **RequestInterceptor** and **PathVariable**. Hope this would help you as we wish we had found this post before! 😇

> **Note** : part of the code displayed was made by my colleague, Alexandre Côté, and I could have found this solution without its help. I think that together we managed to find a better solution, so thanks Alex! 😉