Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Accessing a Spring Data REST API With Feign

DZone's Guide to

Accessing a Spring Data REST API With Feign

Learn how to configure a Spring Boot Feign client to access a Spring Data REST API, saving you a lot of manual programming.

· Integration Zone
Free Resource

Modernize your application architectures with microservices and APIs with best practices from this free virtual summit series. Brought to you in partnership with CA Technologies.

Spring Data REST is a framework that automatically exposes a REST API for Spring Data repositories, thus potentially saving a lot of manual programming work. Feign is a framework that allows easy creation of REST clients and is well integrated into the Spring Cloud ecosystem. Together, both frameworks seem to be a natural fit, especially in a microservice environment.

However, they don’t play along by default. This blog post shows what has to be done in order to be able to access a Spring Data REST API with a Spring Boot Feign client.

The Symptom: Serialization Issues

When accessing a Spring Data REST API with a Feign client, you may trip over serialization issues like this one:

Can not deserialize instance of java.util.ArrayList out of START_OBJECT token

This error occurs when Feign tries to deserialize a JSON object provided by a Spring Data REST server. The cause for this is simply that Spring Data REST by default creates JSON in a Hypermedia format called HAL, and Feign by default does not know how to parse it. The response Spring Data REST creates for a GET request to a collection resource like  http://localhost:8080/addresses  may look something like this:

{
  "_embedded" : {
    "addresses" : [ {
      "street" : "Elm Street",
      "_links" : {...}
      }
    }, {
      "street" : "High Street",
      "_links" : {...}
    } ]
  },
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/addresses/"
    },
    "profile" : {
      "href" : "http://localhost:8080/profile/addresses"
    }
  }
}

The deserialization issue comes from the fact that Feign by default expects a simple array of address objects and instead gets a JSON object.

The Solution: Help Feign Understand Hypermedia

To enable Feign to understand the HAL JSON format, we have to take the following steps.

Add Dependency to Spring HATEOAS

Spring Data REST uses Spring HATEOAS to generate the HAL format on the server side. Spring HATEOAS can just as well be used on the client side to deserialize the HAL-formatted JSON. Thus, simply add the following dependency to your client (Gradle notation):

compile('org.springframework.boot:spring-boot-starter-hateoas')

Enable Spring Boot’s Hypermedia Support

Next, we have to tell our Spring Boot client application to configure its JSON parsers to use Spring HATEOAS. This can be done by simply annotating your Application class with the  @EnableHypermedia  annotation:

@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
@SpringBootApplication
@EnableFeignClients
public class DemoApplication {
  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}

Use Resource and Resources Instead of Your Domain Objects

Feign will still not be able to map HAL-formatted JSON into your domain objects. That’s because your domain objects most likely don’t contain properties like _embedded or  _links that are part of that JSON. To make these properties known to a JSON parser, Spring HATEOAS provides the two generic classes  Resource<?>   and  Resources<?>  .

So, in your Feign client, instead of returning domain objects like  Address  or  List<Address>  return  Resource<Address>   or  Resources<Address>  :

@FeignClient(value = "addresses", path = "/addresses")
public interface AddressClient {

  @RequestMapping(method = RequestMethod.GET, path = "/")
  Resources<Address> getAddresses();

  @RequestMapping(method = RequestMethod.GET, path = "/{id}")
  Resource<Address> getAddress(@PathVariable("id") long id);

}

Feign will then be able to successfully parse the HAL-formatted JSON into the Resource or Resources objects.

Accessing and Manipulating Associations Between Entities With Feign

Once feign is configured to play along with Spring Data REST, simple CRUD operations are just a matter of creating the correct methods annotated with  @RequestMapping .

However, there is still the question of how to access and create associations between entities with Feign, since managing associations with Spring Data Rest is not self-explanatory (see this blog post).

The answer to that is actually also just a matter of creating the correct  @RequestMapping . Assuming that Address has a  @ManyToOne  relationship to Customer, creating an association to an (existing)  Customer  can be implemented with a PUT request of Content-Type  text/uri-list  to the association resource  /addresses/{addressId}/customer  as shown below. The other way around, reading the Customer associated to an  Address can be done with a GET request to the endpoint  /addresses/{addressId}/customer .

@FeignClient(value = "addresses", path = "/addresses")
public interface AddressClient {

  @RequestMapping(method = RequestMethod.PUT, consumes = "text/uri-list", path="/{addressId}/customer")
  Resource<Address> associateWithCustomer(@PathVariable("addressId") long addressId, @RequestBody String customerUri);

  @RequestMapping(method = RequestMethod.GET, path="/{addressId}/customer")
  Resource<Customer> getCustomer(@PathVariable("addressId") long addressId);

}

Example Code

Working code containing the examples above can be found in my GitHub repo.

The Integration Zone is proudly sponsored by CA Technologies. Learn from expert microservices and API presentations at the Modernizing Application Architectures Virtual Summit Series.

Topics:
spring boot ,spring data rest ,spring data ,spring cloud ,feign ,integration

Published at DZone with permission of Tom Hombergs. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}