Jackson encoder cannot encode interface `Body` type
Nobody has claimed this yet.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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