Mastering OpenFeign: From a Simple Demo to Full Spring Cloud Integration

This article walks through a complete OpenFeign tutorial, starting with a basic demo that fetches GitHub contributors and creates issues, then dives into Feign's internal configuration, dynamic proxy generation, annotation parsing, request handling, logging, client execution, error decoding, and finally explains how Spring Cloud OpenFeign initializes and extends these capabilities with Hystrix and Sentinel support.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
Mastering OpenFeign: From a Simple Demo to Full Spring Cloud Integration

0. Intro Demo

This code is a simple OpenFeign example that retrieves all contributors of a GitHub repository and creates an issue, recommended for initial debugging and source code exploration.

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

  @RequestLine("POST /repos/{owner}/{repo}/issues")
  void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);
}

public static class Contributor {
  String login;
  int contributions;
}

public static class Issue {
  String title;
  String body;
  List<String> assignees;
  int milestone;
  List<String> labels;
}

public class MyApp {
  public static void main(String... args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
    List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

Feign.build Dependency Injection

public Feign build() {
  SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =
      new SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,
          logLevel, decode404, closeAfterDecode, propagationPolicy);
  ParseHandlersByName handlersByName =
      new ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,
          errorDecoder, synchronousMethodHandlerFactory);
  return new ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);
}

JDK Dynamic Proxy Generation

Dynamic Proxy Interface Object

public class ReflectiveFeign extends Feign {
  @Override
  public <T> T newInstance(Target<T> target) {
    Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);
    InvocationHandler handler = factory.create(target, methodToHandler);
    T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), new Class<?>[]{target.type()}, handler);
    return proxy;
  }
}

Parsing Interface Method Annotations

To parse the annotations of GitHub.contributors, Feign provides a Contract implementation.

Default Parsing Logic

class Default extends Contract.BaseContract {
  protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
    Class<? extends Annotation> annotationType = methodAnnotation.annotationType();
    if (annotationType == RequestLine.class) {
      // @RequestLine handling logic
    } else if (annotationType == Body.class) {
      // @Body handling logic
    } else if (annotationType == Headers.class) {
      // @Headers handling logic
    }
  }

  protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
    boolean isHttpAnnotation = false;
    for (Annotation annotation : annotations) {
      Class<? extends Annotation> annotationType = annotation.annotationType();
      if (annotationType == Param.class) {
        // @Param handling logic
      } else if (annotationType == QueryMap.class) {
        // @QueryMap handling logic
      } else if (annotationType == HeaderMap.class) {
        // @HeaderMap handling logic
      }
    }
    return isHttpAnnotation;
  }
}

Native Common Annotations

@RequestLine

– Method @Param – Parameter @Headers – Method, Type @QueryMap – Parameter @HeaderMap – Parameter @Body – Method

Spring MVC Extension Annotations

SpringMvcContract adds support for Spring MVC annotations in spring-cloud-open-feign.

public class SpringMvcContract {
  @Override
  protected void processAnnotationOnClass(MethodMetadata data, Class<?> clz) {
    // Process @RequestMapping on class
  }

  @Override
  protected void processAnnotationOnMethod(MethodMetadata data, Annotation methodAnnotation, Method method) {
    // Process @RequestMapping, @GetMapping, @PostMapping, etc.
  }

  @Override
  protected boolean processAnnotationsOnParameter(MethodMetadata data, Annotation[] annotations, int paramIndex) {
    // Process parameter annotations
    return isHttpAnnotation;
  }
}

MethodHandler Request Processing Logic

MethodHandler Routing

Based on the request method, the request is routed to different MethodHandler implementations.

final class SynchronousMethodHandler implements MethodHandler {
  @Override
  public Object invoke(Object[] argv) throws Throwable {
    RequestTemplate template = buildTemplateFromArgs.create(argv);
    Options options = findOptions(argv);
    Retryer retryer = this.retryer.clone();
    while (true) {
      try {
        Request request = targetRequest(template);
        if (logLevel != Logger.Level.NONE) {
          logger.logRequest(metadata.configKey(), logLevel, request);
        }
        Response response = client.execute(request, options);
        // Decode response based on status code
        return response;
      } catch (RetryableException e) {
        // Retry logic
      }
    }
  }
}

Building Request Templates

Depending on the method, the request body may be a form submission or a raw body.

Executing Request Interceptors

Request targetRequest(RequestTemplate template) {
  for (RequestInterceptor interceptor : requestInterceptors) {
    interceptor.apply(template);
  }
  return target.apply(template);
}

Request Logging Levels

public enum Level {
  NONE,   // No logging
  BASIC,  // Log method, URL, status, time
  HEADERS,// Log headers + basic info
  FULL    // Log headers, body + basic info
}

Client Execution of Final Request

The default client uses java.net and can be replaced with high‑performance implementations such as HttpClient or OKHttp.

class Default implements Client {
  @Override
  public Response execute(Request request, Request.Options options) throws IOException {
    HttpURLConnection connection = convertAndSend(request, options);
    return convertResponse(connection, request);
  }
}

Spring Cloud Load Balancer Client

public class FeignBlockingLoadBalancerClient {
  @Override
  public Response execute(Request request, Request.Options options) throws IOException {
    URI originalUri = URI.create(request.url());
    String serviceId = originalUri.getHost();
    ServiceInstance instance = loadBalancerClient.choose(serviceId);
    String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
    Request newRequest = Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.requestBody());
    return delegate.execute(newRequest, options);
  }
}

Response Decoder Handling

class Default implements Encoder {
  @Override
  public void encode(Object object, Type bodyType, RequestTemplate template) {
    if (bodyType == String.class) {
      template.body(object.toString());
    } else if (bodyType == byte[].class) {
      template.body((byte[]) object, null);
    } else if (object != null) {
      throw new EncodeException(String.format("%s is not a type supported by this encoder.", object.getClass()));
    }
  }
}
public static class Default implements ErrorDecoder {
  private final RetryAfterDecoder retryAfterDecoder = new RetryAfterDecoder();

  @Override
  public Exception decode(String methodKey, Response response) {
    FeignException exception = errorStatus(methodKey, response);
    Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
    if (retryAfter != null) {
      return new RetryableException(response.status(), exception.getMessage(), response.request().httpMethod(), exception, retryAfter, response.request());
    }
    return exception;
  }

  private <T> T firstOrNull(Map<String, Collection<T>> map, String key) {
    if (map.containsKey(key) && !map.get(key).isEmpty()) {
      return map.get(key).iterator().next();
    }
    return null;
  }
}

Injecting a custom ErrorDecoder is common practice.

Spring Cloud OpenFeign Extension

EnableFeignClients Parsing

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(FeignClientsRegistrar.class)
public @interface EnableFeignClients {
}

Adding @EnableFeignClients on the main class activates Spring Cloud OpenFeign functionality.

FeignClientsRegistrar

class FeignClientsRegistrar {
  @Override
  public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    registerFeignClients(metadata, registry);
  }

  public void registerFeignClients(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    // Scan for @FeignClient within configured base packages and register them
  }

  private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
    String className = annotationMetadata.getClassName();
    BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(FeignClientFactoryBean.class);
    // Set properties such as url, path, name, contextId, type, decode404, fallback, fallbackFactory
    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    // Register bean definition
  }
}

Default Auto‑Configuration

public class FeignAutoConfiguration {
  @Configuration(proxyBeanMethods = false)
  @ConditionalOnMissingClass("feign.hystrix.HystrixFeign")
  protected static class DefaultFeignTargeterConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public Targeter feignTargeter() {
      return new DefaultTargeter();
    }
  }
}

HystrixFeign

public final class HystrixFeign {
  public static Builder builder() {
    return new Builder();
  }

  public static final class Builder extends Feign.Builder {
    @Override
    public Feign build(FallbackFactory<?> nullableFallbackFactory) {
      super.invocationHandlerFactory((target, dispatch) -> new HystrixInvocationHandler(target, dispatch, setterFactory, nullableFallbackFactory));
      super.contract(new HystrixDelegatingContract(contract));
      return super.build();
    }
  }
}
final class HystrixInvocationHandler implements InvocationHandler {
  @Override
  public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
    HystrixCommand<Object> hystrixCommand = new HystrixCommand<Object>(setterMethodMap.get(method)) {
      @Override
      protected Object run() throws Exception {
        return HystrixInvocationHandler.this.dispatch.get(method).invoke(args);
      }
    };
    return hystrixCommand.execute();
  }
}

SentinelFeign

public final class SentinelFeign {
  // Similar to HystrixFeign but uses Sentinel for fault tolerance
}
public class SentinelInvocationHandler implements InvocationHandler {
  @Override
  public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
    try {
      ContextUtil.enter(resourceName);
      entry = SphU.entry(resourceName, EntryType.OUT, 1, args);
      return methodHandler.invoke(args);
    } catch (Throwable ex) {
      // fallback logic
      throw ex;
    } finally {
      ContextUtil.exit();
    }
  }
}

Summary Sequence Diagram

Future Plans

Upcoming articles will cover source‑code analysis of Ribbon, Hystrix, Sentinel, Nacos and other related components.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaSpring CloudOpenFeignFeign Clients
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.