OpenFeign / OpenFeign/feign

Jackson encoder cannot encode interface `Body` type

Open
#1,608 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feedback provided
Dominant language
Java
Stars
9.8k
Forks
1.9k
Avg merge
1d 2h
Merged PRs (30d)
41

Description

The Jackson encoder does not seem to be able to encode the body when the type is an interface and the runtime type is an implementation of that interface. I've created a demo Gradle project here.

The Feign client definition:

public interface FooClient {
     // Encoding for this method will fail.
    @RequestLine("POST /submit")
    @Headers("Content-Type: application/json")
    Response createFoo(Foo foo);

    // Encoding for this method will succeed.
    @RequestLine("POST /submit")
    @Headers("Content-Type: application/json")
    Response createFooImpl(FooImpl foo);

    interface Foo { int id(); }

    record FooImpl(int id) implements Foo { }
}

The test definition that demonstrates the issue

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class FooClientTest {

    @LocalServerPort int port;

    @Test
    public void clientTest() {
        final FooClient lClient = Feign.builder()
            .encoder(new JacksonEncoder(List.of(
                // Possibly this would be necessary with the original encoder implementation.
//                new FooModule()
            )))
            .target(FooClient.class, String.format("http://localhost:%s", port));

        Response response = lClient.createFooImpl(new FooImpl(10));
        // Expect 404 because there is no service definition.
        assertThat(response.status()).isEqualTo(404);

        response = lClient.createFoo(new FooImpl(10));
        // Expect 404 because there is no service definition.
       // This throws an encoding exception.
        assertThat(response.status()).isEqualTo(404);
    }

    public static class FooModule extends SimpleModule {
        {
            addAbstractTypeMapping(Foo.class, FooImpl.class);
        }
    }
}

The exception thrown is:

feign.codec.EncodeException: No serializer found for class codes.asm.feign.mcce.client.FooClient$FooImpl and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

at feign.jackson.JacksonEncoder.encode(JacksonEncoder.java:54)

This seems to be a result of the JacksonEncoder explicitly invoking the writer for the interface type.

JavaType javaType = mapper.getTypeFactory().constructType(bodyType);
template.body(mapper.writerFor(javaType).writeValueAsBytes(object), Util.UTF_8);

This was done in this commit. From some testing I've done, I believe the original code would work and encode the implementation correctly, possibly requiring some basic configuration using a Jackson Module. Using the same Jackson module to configure the Spring service endpoint and test it with MockMvc correctly encodes the implementation class and decodes to the parameter within the service method.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at JacksonEncoder.java line 54 and reproduce the failure with the linked Gradle demo, comparing interface-typed and implementation-typed request bodies. Verify the behavior against the FooClientTest scenario; done means an implementation passed through an interface parameter is encoded successfully while the concrete-type case continues to work.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
api, backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.