5

I've got a server that exposes resources through spring-data-rest and this uses, as far as I understand HAL or HATEOAS. But when I try to use it in combination with Feign, I can't seem to be able to register a Jackson2HalModule that gets picked up.

Is there something I have to do to connect the Feign "client" to the new converter? Does it use another ObjectMapper than the one I got here?

Code:

@Inject
public void configureObjectMapper(ObjectMapper mapper, RestTemplate template) {
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.registerModule(new Jackson2HalModule());

    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setSupportedMediaTypes(MediaType.parseMediaTypes("application/hal+json"));
    converter.setObjectMapper(mapper);

    template.getMessageConverters().add(converter);
}

Response from server:

{
  "_links" : {
    "self" : {
      "href" : "http://localhost:13372/user{?page,size,sort}",
      "templated" : true
    },
    "search" : {
      "href" : "http://localhost:13372/user/search"
    }
  },
  "_embedded" : {
    "user" : [ {
      "id" : "5567613e5da543dba4201950",
      "version" : 0,
      "created" : "2015-05-28T18:41:02.685Z",
      "createdBy" : "system test",
      "edited" : "2015-05-28T18:41:02.713Z",
      "editedBy" : "system test",
      "username" : "devuser",
      "email" : "dev@test.com",
      "roles" : [ "USER" ],
      "_links" : {
        "self" : {
          "href" : "http://localhost:13372/user/5567613e5da543dba4201950"
        }
      }
    } ]
  },
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 0
  }
}

Exception:

Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
 at [Source: java.io.PushbackInputStream@7b6c6e70; line: 1, column: 1]
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
    at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:762)
    at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:758)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.handleNonArray(CollectionDeserializer.java:275)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:216)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:206)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:25)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3066)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2221)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:205)
LG87
  • 695
  • 1
  • 10
  • 20

5 Answers5

5

I found the problem. The Exception occured due to the fact that the response from the REST API was a single response. So it failed to see it as a List of entities.

When I added (building on the code above):

mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

It works

Edit: On a side note, I had implemented the FeignClient like this:

@Service
@FeignClient(UsersConstants.USER_SERVICE_NAME)
public interface UsersServices {

    @RequestMapping(method = RequestMethod.GET, value = "/user")
    List<User> getUsers();
}

But how it should be, since it's a pageable resource:

@Service
@FeignClient(UsersConstants.USER_SERVICE_NAME)
public interface UsersServices {

    @RequestMapping(method = RequestMethod.GET, value = "/user")
    List<PagedResources<User>> getUsers();
}

The PagedResource is found within HATEOAS dependency:

<dependency>
    <groupId>org.springframework.hateoas</groupId>
    <artifactId>spring-hateoas</artifactId>
</dependency>

It also has a lot of other classes that can help out, such as Resource, Resources and so forth.

LG87
  • 695
  • 1
  • 10
  • 20
  • I have tried the same thing but I cant get it working. Do I have to write the '@inject' method in a different class or is it enough in main class where i have '@SpringBootApplication' annotation. @LG87 – Randika Hapugoda Jun 23 '15 at 12:54
  • As far as I know, it should work in whatever class is managed by Spring as a component – LG87 Jul 02 '15 at 07:44
5

This worked for me:

@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
@SpringBootApplication
@EnableFeignClients
public class Application {
....
}

Note the @EnableHypermediaSupport

@FeignClient(url = "http://localhost:8080")
public interface PersonClient {
    @RequestMapping(method = RequestMethod.GET, value = "/persons")
    Resources<Person> getPersons();

    @RequestMapping(method = RequestMethod.GET, value = "/persons/{id}")
    Resource<Person> getPerson(@PathVariable("id") long id);
}

I have created a fully working example here: https://github.com/jtdev/spring-feign-data-rest-example

Note that simply switchen the maven pom from spring-boot to spring-cloud (without changing code), may easily result in json errors.

Devabc
  • 4,782
  • 5
  • 27
  • 40
  • can we map all the requests from spring data rest end point to a single faign client? In above example all methods like GET,POST,PATCH in to a single end point without declaring them in the interface? – Randika Hapugoda Feb 10 '16 at 09:38
  • I've looked at your example code and it's good when you don't have to mess around with ObjectMapper and Converters. Now, what is the minimal change that you would add in order to add a Java 8 ZonedDateTime field into your Person object? – Luan Nguyen Apr 15 '16 at 16:12
  • Adding `@EnableHypermediaSupport` worked for me. Strange that this is necessary for Feign; I first had another solution that worked with `RestTemplate` that didn't require this. – Jesper May 22 '16 at 13:25
3

You should define a feignDecoder bean in your application. If you have spring-hateoas in your environment then try something like this:

@Bean
public Decoder feignDecoder() {
    ObjectMapper mapper = new ObjectMapper()
            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
            .registerModule(new Jackson2HalModule());

    return new ResponseEntityDecoder(new JacksonDecoder(mapper));
}

Then you can consume your HAL as PagedResource.

flecno
  • 76
  • 2
0

I got this to work for me (Thanks @Devabc, you're example helped me a lot):

I wanted to get PagedResources of a Resource for a User.

Remember to add @EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL) to your main application class.

My Feign client looks as follows:

@FeignClient("user-microservice")
public interface UserClient {
  @RequestMapping(method = RequestMethod.GET, value = "/user")
  PagedResources<Resource<User>> findAll();
}

Also remember to add a default and parameterised constructor for your model. (in my case User) I'm not sure why but this seemed to fix my serialisation issue that I had.

Lastly I Used this version of Feign

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-feign</artifactId>
  <version>1.1.5.RELEASE</version>
</dependency>
0

check the link

The comment below from Greg Turnquist did work for collection type return

C) the type to extract from the collection should be Resources<Resource<Question>>. The collection has links as does each each entry of the collection.

R-JANA
  • 1,138
  • 2
  • 14
  • 30