Careeroza
How Feign Works Inside
Dynamic Proxies
There is no class implementing ProductClient in your code. At startup Feign creates a JDK dynamic proxy: an object that implements the interface and sends every method call to one handler. The handler reads the annotations, builds the URL, makes the HTTP call and converts the JSON. Here is a tiny "MiniFeign" with a fake HTTP layer:
Example
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
public class Main {
public static void main(String[] args) {
FakeHttp http = new FakeHttp();
ProductClient client = MiniFeign.create(ProductClient.class, "http://product-service", http);
System.out.println("Is a proxy: " + Proxy.isProxyClass(client.getClass()));
System.out.println(client.getProduct(7));
System.out.println(client.byCategory("Audio", 0));
}
}
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface Get { String value(); }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @interface PathVar { String value(); }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @interface Param { String value(); }
interface ProductClient {
@Get("/api/products/{id}")
String getProduct(@PathVar("id") long id);
@Get("/api/products")
String byCategory(@Param("category") String category, @Param("page") int page);
}
class MiniFeign {
@SuppressWarnings("unchecked")
static <T> T create(Class<T> api, String baseUrl, FakeHttp http) {
InvocationHandler handler = (proxy, method, args) -> {
String path = method.getAnnotation(Get.class).value();
StringBuilder query = new StringBuilder();
Parameter[] params = method.getParameters();
for (int i = 0; i < params.length; i++) {
PathVar pv = params[i].getAnnotation(PathVar.class);
Param qp = params[i].getAnnotation(Param.class);
if (pv != null) path = path.replace("{" + pv.value() + "}", String.valueOf(args[i]));
if (qp != null) query.append(query.isEmpty() ? "?" : "&").append(qp.value()).append('=').append(args[i]);
}
String url = baseUrl + path + query;
System.out.println(" " + method.getName() + "(...) -> GET " + url);
return http.get(url);
};
return (T) Proxy.newProxyInstance(api.getClassLoader(), new Class<?>[] {api}, handler);
}
}
class FakeHttp {
private final Map<String, String> responses = Map.of(
"http://product-service/api/products/7", "{\"id\":7,\"name\":\"boAt Rockerz 450\",\"price\":1499.00}",
"http://product-service/api/products?category=Audio&page=0", "[{\"id\":7},{\"id\":9}]");
String get(String url) {
return responses.getOrDefault(url, "404 Not Found");
}
}Output
Is a proxy: true
getProduct(...) -> GET http://product-service/api/products/7
{"id":7,"name":"boAt Rockerz 450","price":1499.00}
byCategory(...) -> GET http://product-service/api/products?category=Audio&page=0
[{"id":7},{"id":9}]What Real Feign Adds
- Contract: reads Spring MVC annotations (
@GetMapping,@RequestParam...). - Encoder / Decoder: Jackson converts request bodies and JSON responses into your records.
- Load balancer: replaces
http://product-servicewith a real instance from Eureka. - Interceptors, ErrorDecoder, Retryer, logging and timeouts (next topics).
Tip: In an interview, "How does Feign work without an implementation class?" — answer: JDK dynamic proxy + annotation metadata. Spring registers a FeignClientFactoryBean for each interface that produces that proxy.