Spring Cloud OpenFeign on Spring Boot 3.x: Declarative REST Clients with Eureka and Resilience4j

Feign occupies an odd spot in 2026: the Netflix incarnation is long dead, but its community successor — Spring Cloud OpenFeign — is alive, widely deployed, and in “feature-complete” maintenance, while Spring’s own HTTP interface clients are the stated future. This rewrite of my 2020 Feign tutorial shows declarative REST clients on Spring Boot 3.x the modern way: OpenFeign with Eureka and Spring Cloud LoadBalancer, Resilience4j fallbacks, and an honest section on when to pick @HttpExchange instead.

Tested with: Spring Boot 3.4, Spring Cloud 2024.0 (OpenFeign 4.2), Java 21. Part of the modernized series anchored by the Spring Cloud Netflix migration guide.

Netflix Feign vs OpenFeign vs HTTP Interfaces

Netflix FeignSpring Cloud OpenFeignHTTP Interface (@HttpExchange)
StatusDeadMaintained, feature-completeActively developed (Spring’s direction)
Packagescom.netflix.feignfeign.* + org.springframework.cloud.openfeignorg.springframework.web.service
Extra starter—spring-cloud-starter-openfeignNone (Spring 6 built-in)
AnnotationsFeign-nativeSpring MVC style (@GetMapping)@GetExchange / @PostExchange
Load balancingRibbon (dead)Spring Cloud LoadBalancerVia @LoadBalanced builder

If you have an existing Feign codebase, OpenFeign on Boot 3.x is a low-effort, fully supported home. If you are starting fresh — especially on Boot 4 — read the last section before committing; HTTP interface clients may be the better default.

Setup

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- with Eureka: service names resolve via Spring Cloud LoadBalancer -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
@SpringBootApplication
@EnableFeignClients   // scans for @FeignClient interfaces
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

The Client Interface

// name = Eureka service ID — no URL, no Ribbon, no host hardcoding
@FeignClient(name = "student-service")
public interface StudentClient {

    @GetMapping("/students")
    List<Student> findAll();

    @GetMapping("/students/{id}")
    Student findById(@PathVariable("id") int id);   // ALWAYS name the variable — see pitfalls

    @PostMapping("/students")
    Student create(@RequestBody Student student);
}

record Student(int id, String name, String branch) {}
@Service
public class EnrollmentService {

    private final StudentClient studentClient;

    public EnrollmentService(StudentClient studentClient) {
        this.studentClient = studentClient;   // inject the interface; Feign builds the proxy
    }

    public List<Student> activeStudents() {
        return studentClient.findAll();
    }
}

For external (non-Eureka) APIs, swap name for url = "https://api.example.com" — everything else is identical.

Timeouts, Logging, and Configuration

spring:
  cloud:
    openfeign:
      client:
        config:
          default:                  # applies to every client
            connect-timeout: 2000
            read-timeout: 5000
            logger-level: basic
          student-service:          # per-client override
            read-timeout: 3000

Note the property prefix: it is spring.cloud.openfeign.* on Boot 3.x — the old feign.client.* prefix from 2020-era tutorials no longer binds, and unbound timeout config means infinite defaults. This is the most common silent misconfiguration I see in migrated projects.

Resilience: Fallbacks with Resilience4j

Hystrix-era Feign had fallback support built around Hystrix; the modern wiring goes through Spring Cloud CircuitBreaker backed by Resilience4j:

spring:
  cloud:
    openfeign:
      circuitbreaker:
        enabled: true
@FeignClient(name = "student-service", fallback = StudentClientFallback.class)
public interface StudentClient { /* as above */ }

@Component
class StudentClientFallback implements StudentClient {
    @Override public List<Student> findAll() { return List.of(); }
    @Override public Student findById(int id) { return new Student(id, "unknown", "unknown"); }
    @Override public Student create(Student s) {
        throw new ServiceUnavailableException("student-service down; create queued");
    }
}

Design note from production: returning empty collections is a fine fallback for reads; for writes, fail loudly or queue — a fallback that pretends a POST succeeded is a data-loss bug wearing a resilience costume.

Pitfalls

1. Unnamed @PathVariable. Feign proxies cannot always infer parameter names (it depends on the -parameters compiler flag). The resulting error — PathVariable annotation was empty on param 0 — reads like a framework bug but is fixed by writing @PathVariable("id") explicitly. Same for @RequestParam.

2. FeignException on every non-2xx. Feign throws FeignException for 404s by default, where RestClient users expect typed handling. Either catch and translate per call, or register an ErrorDecoder bean to map status codes to your domain exceptions in one place.

3. Inheriting server contracts. It is tempting to share one interface between the @RestController and the @FeignClient. Resist it: the official docs warn against contract inheritance, and in practice it couples deployables so a server-side refactor breaks every consumer’s build.

4. The old property prefix. Covered above — feign.client.* config silently not binding is the first thing to check when timeouts “don’t work”.

When to Use @HttpExchange Instead

Spring’s HTTP interface clients give you the same declarative style with no extra starter, RestClient underneath, and first-class Boot 4 registration. My decision rule: existing Feign estate or need for Feign-specific features (ErrorDecoder, per-client config tree, contract customization) → stay on OpenFeign; new Boot 3.2+/4 services → start with @HttpExchange. The interface shape is so similar that a later conversion is mostly annotation renames — the comparison and registration details are in Spring Boot 4 HTTP Service Clients.

AI Prompts for Feign Work

Migrate Netflix-Era Clients

Upgrade this Netflix Feign / Spring Boot 2.x client to Spring Cloud OpenFeign on Boot 3.x: [paste interface and config here]. Fix package imports, convert feign.client.* properties to spring.cloud.openfeign.*, replace Hystrix fallback wiring with the CircuitBreaker integration, and name every @PathVariable explicitly.

What it does: The full legacy-to-modern conversion including both silent breakages (property prefix, parameter names).

When to use it: Per client interface during a Boot 2 → 3 migration.

Generate a Client from an API

From this OpenAPI spec or controller: [paste here], generate a @FeignClient interface with DTO records, explicit parameter names, and per-client timeout configuration appropriate for a [latency profile] dependency.

What it does: Produces the boilerplate — interface, records, config block — in one consistent pass.

When to use it: When wiring a new downstream dependency.

Design an ErrorDecoder

My Feign clients call these services with these error contracts: [paste status-code/error-body conventions here]. Write an ErrorDecoder that maps them to domain exceptions, preserving retryable vs non-retryable classification for Resilience4j.

What it does: Centralizes error translation instead of FeignException catches scattered across services.

When to use it: Once you have more than two or three Feign clients.

Audit Fallback Safety

Review these Feign fallback classes: [paste here]. Flag any fallback on a write operation that returns a fabricated success, and propose safe alternatives (queueing, explicit ServiceUnavailable, compensation) for each.

What it does: Catches the data-loss-disguised-as-resilience pattern before production does.

When to use it: Code review for any client with a fallback class.

Decide Feign vs HTTP Interfaces

My project: [describe Boot version, number of clients, Feign features used]. Recommend OpenFeign vs @HttpExchange for new clients and whether converting existing ones is worth it, with a conversion-effort estimate per feature in use.

What it does: Turns the strategic question into a concrete, feature-by-feature recommendation.

When to use it: At architecture-decision time on any Boot 3.2+ codebase.

Conclusion

OpenFeign on Spring Boot 3.x is a solid, supported home for declarative clients: service-ID resolution via LoadBalancer, fallbacks via Resilience4j, configuration under the new spring.cloud.openfeign prefix. Name your path variables, decode errors centrally, never fake success in a write fallback — and for greenfield Boot 4 services, give @HttpExchange the first look.

See Also

Further Reading

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.