modules) {
+ this(
+ JsonMapper.builder()
+ .changeDefaultPropertyInclusion(
+ incl -> incl.withValueInclusion(JsonInclude.Include.NON_NULL))
+ .enable(SerializationFeature.INDENT_OUTPUT)
+ .addModules(modules)
+ .build());
+ }
+
+ public Jackson3Encoder(JsonMapper mapper) {
+ this.mapper = mapper;
+ }
+
+ @Override
+ public void encode(Object object, Type bodyType, RequestTemplate template) {
+ try {
+ JavaType javaType = mapper.getTypeFactory().constructType(bodyType);
+ template.body(mapper.writerFor(javaType).writeValueAsBytes(object), Util.UTF_8);
+ } catch (JacksonException e) {
+ throw new EncodeException(e.getMessage(), e);
+ }
+ }
+}
diff --git a/jackson3/src/main/java/feign/jackson3/Jackson3IteratorDecoder.java b/jackson3/src/main/java/feign/jackson3/Jackson3IteratorDecoder.java
new file mode 100644
index 0000000000..9ba52fa593
--- /dev/null
+++ b/jackson3/src/main/java/feign/jackson3/Jackson3IteratorDecoder.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright © 2012 The Feign Authors (feign@commonhaus.dev)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package feign.jackson3;
+
+import static feign.Util.UTF_8;
+import static feign.Util.ensureClosed;
+
+import feign.Response;
+import feign.Util;
+import feign.codec.DecodeException;
+import feign.codec.Decoder;
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Reader;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import tools.jackson.core.JacksonException;
+import tools.jackson.core.JsonParser;
+import tools.jackson.core.JsonToken;
+import tools.jackson.databind.DeserializationFeature;
+import tools.jackson.databind.JacksonModule;
+import tools.jackson.databind.ObjectReader;
+import tools.jackson.databind.json.JsonMapper;
+
+/**
+ * Jackson 3 decoder which return a closeable iterator. Returned iterator auto-close the {@code
+ * Response} when it reached json array end or failed to parse stream. If this iterator is not
+ * fetched till the end, it has to be casted to {@code Closeable} and explicity {@code
+ * Closeable#close} by the consumer.
+ *
+ *
+ *
+ *
+ *
+ *
Example:
+ *
+ *
+ *
+ * Feign.builder()
+ * .decoder(Jackson3IteratorDecoder.create())
+ * .doNotCloseAfterDecode() // Required to fetch the iterator after the response is processed, need to be close
+ * .target(GitHub.class, "https://api.github.com");
+ * interface GitHub {
+ * {@literal @}RequestLine("GET /repos/{owner}/{repo}/contributors")
+ * Iterator contributors(@Param("owner") String owner, @Param("repo") String repo);
+ * }
+ *
+ */
+public final class Jackson3IteratorDecoder implements Decoder {
+
+ private final JsonMapper mapper;
+
+ Jackson3IteratorDecoder(JsonMapper mapper) {
+ this.mapper = mapper;
+ }
+
+ @Override
+ public Object decode(Response response, Type type) throws IOException {
+ if (response.status() == 404 || response.status() == 204) return Util.emptyValueOf(type);
+ if (response.body() == null) return null;
+ Reader reader = response.body().asReader(UTF_8);
+ if (!reader.markSupported()) {
+ reader = new BufferedReader(reader, 1);
+ }
+ try {
+ // Read the first byte to see if we have any data
+ reader.mark(1);
+ if (reader.read() == -1) {
+ return null; // Eagerly returning null avoids "No content to map due to end-of-input"
+ }
+ reader.reset();
+ return new Jackson3Iterator