# Calling Other Services — OpenFeign, Timeouts and Resilience4j Circuit Breakers

Source: https://skills101.in/learn/spring-boot/spring-cloud-openfeign-resilience4j — Skills101 free tutorial (Advanced Java & Spring Boot). Updated 2026-11-09.

`order-service` must ask `course-api` for a course's title and fee before accepting an order. Calls over the network are slow and can fail — so we need a clean way to call, and a plan for when the other service is down.

## 1. OpenFeign: an HTTP client you don't write

```java
// File: order-service/src/main/java/com/skills101/orders/CourseDto.java
package com.skills101.orders;

// The fields we need from course-api's JSON (extra fields are ignored)
public record CourseDto(Long id, String title, int fee) {
}
```

```java
// File: order-service/src/main/java/com/skills101/orders/CourseClient.java
package com.skills101.orders;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

// Just an interface: Feign writes the HTTP call. "course-api" is looked up in Eureka.
@FeignClient(name = "course-api", fallback = CourseClientFallback.class)
public interface CourseClient {

    @GetMapping("/api/courses/{id}")
    CourseDto getCourse(@PathVariable("id") Long id);
}
```

```java
// File: order-service/src/main/java/com/skills101/orders/OrderServiceApplication.java
package com.skills101.orders;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients   // create implementations for @FeignClient interfaces
public class OrderServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}
```

- You write an **interface** with Spring MVC annotations; Feign generates the code that sends `GET /api/courses/{id}` and turns the JSON into a `CourseDto`.
- `name = "course-api"` is looked up in **Eureka**, so there's no host name in the code.
- `CourseDto` lists only the fields we need — unknown JSON fields (like `seats`) are ignored.

(Spring's own **`RestClient`** with `@HttpExchange` interfaces is the modern built-in alternative; Feign is still very common in existing projects.)

## 2. When course-api fails: a fallback

```java
// File: order-service/src/main/java/com/skills101/orders/CourseClientFallback.java
package com.skills101.orders;

import org.springframework.stereotype.Component;

// Used when course-api is down, slow or failing (or the circuit breaker is open)
@Component
public class CourseClientFallback implements CourseClient {

    @Override
    public CourseDto getCourse(Long id) {
        return new CourseDto(id, null, 0);   // title null = "we couldn't check right now"
    }
}
```

```java
// File: order-service/src/main/java/com/skills101/orders/OrderService.java
package com.skills101.orders;

import java.util.concurrent.atomic.AtomicLong;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    public static final String TOPIC = "orders";

    private final CourseClient courses;
    private final KafkaTemplate<String, OrderPlacedEvent> kafka;
    private final AtomicLong ids = new AtomicLong();   // a real service would use a database

    public OrderService(CourseClient courses, KafkaTemplate<String, OrderPlacedEvent> kafka) {
        this.courses = courses;
        this.kafka = kafka;
    }

    public OrderResponse place(OrderRequest request) {
        CourseDto course = courses.getCourse(request.courseId());   // synchronous call to course-api
        long orderId = ids.incrementAndGet();
        if (course.title() == null) {
            // course-api is unavailable: accept the order and confirm it later instead of failing
            return new OrderResponse(orderId, request.courseId(), null, 0, "ON_HOLD");
        }
        // asynchronous: publish an event; payments, email and analytics react in their own time
        kafka.send(TOPIC, String.valueOf(orderId), new OrderPlacedEvent(orderId, course.id(), request.email(), course.fee()));
        return new OrderResponse(orderId, course.id(), course.title(), course.fee(), "PLACED");
    }
}
```

Instead of failing the customer's order with a 500 error, we accept it **ON_HOLD** and confirm it later. Choosing a sensible fallback is a business decision: cached data, a default value, a queued retry, or a friendly error.

## 3. Circuit breaker

If course-api is down, waiting for every call to time out wastes threads and makes everything slow — failures **cascade**. A **circuit breaker** watches the calls:

![Circuit breaker states: CLOSED lets calls through; when the failure rate reaches 50% it becomes OPEN and fails fast to the fallback; after 10 seconds it goes HALF-OPEN to test a few calls, and returns to CLOSED if they succeed](https://skills101.in/learn-img/spring-boot/circuit-breaker.svg)

All of it is configuration:

```properties
# File: order-service/src/main/resources/application.properties (excerpt)
spring.cloud.openfeign.circuitbreaker.enabled=true
spring.cloud.openfeign.client.config.default.connect-timeout=2000
spring.cloud.openfeign.client.config.default.read-timeout=2000

# Resilience4j: open the breaker when half of the last 10 calls fail; try again after 10 s
resilience4j.circuitbreaker.configs.default.sliding-window-size=10
resilience4j.circuitbreaker.configs.default.failure-rate-threshold=50
resilience4j.circuitbreaker.configs.default.wait-duration-in-open-state=10s
```

| Pattern | Protects against |
|---|---|
| **Timeout** | a slow service holding your threads forever |
| **Retry** (with backoff) | short, temporary glitches — only for safe, idempotent calls |
| **Circuit breaker** | hammering a service that is clearly down |
| **Bulkhead** | one slow dependency using up all your threads |
| **Rate limiter** | too many calls per second |

Resilience4j provides all five.

## 4. The test

The test starts order-service with a fake course-api: course **1** answers normally, course **2** returns **HTTP 500**.

```java
// File: order-service/src/test/java/com/skills101/orders/OrderServiceApplicationTests.java (excerpt)
static HttpServer stub() {
    try {
        HttpServer s = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
        s.createContext("/api/courses/1", ex -> {
            byte[] body = "{\"id\":1,\"title\":\"Java Full Stack\",\"fee\":4999,\"seats\":30}".getBytes(StandardCharsets.UTF_8);
            ex.getResponseHeaders().add("Content-Type", "application/json");
            ex.sendResponseHeaders(200, body.length);
            ex.getResponseBody().write(body);
            ex.close();
        });
        s.createContext("/api/courses/2", ex -> {
            ex.sendResponseHeaders(500, -1);
            ex.close();
        });
        s.start();
        return s;
```

Ordering course 1 → **201**:

<!-- expect: order-service/order-placed.json -->
```json
{ "orderId": 1, "courseId": 1, "course": "Java Full Stack", "fee": 4999, "status": "PLACED" }
```

Ordering course 2 (course-api fails, fallback used) → still **201**, but on hold:

<!-- expect: order-service/order-on-hold.json -->
```json
{ "orderId": 2, "courseId": 2, "course": null, "fee": 0, "status": "ON_HOLD" }
```

## Common mistakes

- No timeouts — the default can be far too long.
- Retrying non-idempotent calls (e.g. "charge card") and charging twice.
- A fallback that hides real problems without logging or metrics.
- Calling services in a long synchronous chain (A → B → C → D): latency and failure add up. Use events (next lesson) where possible.

## Quick recap

- OpenFeign: declare an interface; Feign + Eureka do the HTTP call.
- Always set timeouts; add a fallback with a sensible business meaning.
- Circuit breaker: CLOSED → OPEN (fail fast) → HALF-OPEN (test) → CLOSED.
- Resilience4j: circuit breaker, retry, timeout, bulkhead, rate limiter.


Practice quiz and free certificate: https://skills101.in/learn/spring-boot/spring-cloud-openfeign-resilience4j