The Circuit breaker pattern in Java

The Circuit Breaker pattern is used to prevent an application from performing an operation that is likely to fail. In microservices architecture, it’s commonly used to handle fault tolerance when calling external services that may fail or take a long time to respond.

Imagine a scenario where a microservice (Service A) calls another service (Service B). If Service B is down or slow to respond, without a circuit breaker, Service A will continue making requests, which may lead to cascading failures or resource exhaustion. The Circuit Breaker pattern “opens” and stops calling the failing service for a period to allow it to recover, then “closes” after the service is healthy again.

Key states of Circuit Breaker:

  1. Closed: The circuit breaker allows requests to pass through as normal.
  2. Open: The circuit breaker prevents requests from being sent, returning a fallback response immediately.
  3. Half-Open: The circuit breaker allows a limited number of requests to check if the service has recovered.

Implementation

There are different ways to implement Circuit Breaker pattern. In this case we’ll use Resilience4j for implementing the Circuit Breaker in Spring Boot. Add the following dependency to your pom.xml:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot2</artifactId>
    <version>1.7.0</version>
</dependency>

Here’s an example of how we might use a RestTemplate to call another service:

import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class ExternalService {
    @Autowired
    private RestTemplate restTemplate;
    public String callExternalService() {
        String url = "http://localhost:8081/external-api";
        return restTemplate.getForObject(url, String.class);
    }
}

This method will call an external API, but what happens if this API is down or slow? This is where the Circuit Breaker comes in.

To apply the Circuit Breaker, annotate the method that makes the external call using the @CircuitBreaker annotation.

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
import org.springframework.stereotype.Service;
@Service
public class ExternalService {
    @Autowired
    private RestTemplate restTemplate;
    private static final String EXTERNAL_SERVICE = "externalService";
    @CircuitBreaker(name = EXTERNAL_SERVICE, fallbackMethod = "fallbackResponse")
    public String callExternalService() {
        String url = "http://localhost:8081/external-api";
        return restTemplate.getForObject(url, String.class);
    }
    public String fallbackResponse(Throwable t) {
        return "External Service is currently unavailable. Please try again later.";
    }
}

@CircuitBreaker(name = "externalService"): This specifies that Resilience4j will manage the circuit breaker with the name externalService.

fallbackMethod = "fallbackResponse": If the circuit breaker is in the OPEN state, or if the call to the external service fails, this fallback method will be executed.

Now we can configure the circuit breaker properties in our application.yml file.

resilience4j.circuitbreaker:
  instances:
    externalService:
      slidingWindowSize: 5
      failureRateThreshold: 50
      waitDurationInOpenState: 10000
      permittedNumberOfCallsInHalfOpenState: 3
  • slidingWindowSize: The number of calls to evaluate for the failure rate,
  • failureRateThreshold: The percentage of failures that trip the circuit breaker. In this case, if 50% of calls fail, the circuit opens,
  • waitDurationInOpenState: The amount of time (in milliseconds) the circuit breaker stays open before transitioning to half-open state,
  • permittedNumberOfCallsInHalfOpenState: The number of calls to allow in the half-open state to check if the external service has recovered.

How it works

  1. Initially, the circuit breaker is in the Closed state. All requests pass through to the external service.
  2. If the failure rate (e.g., timeouts or errors) exceeds the failureRateThreshold, the circuit breaker transitions to the Open state. In this state, all requests are blocked, and the fallback method is executed.
  3. After the waitDurationInOpenState, the circuit breaker transitions to the Half-Open state, allowing a limited number of requests to check if the service has recovered.
  4. If the service responds successfully during the Half-Open state, the circuit breaker transitions back to Closed. Otherwise, it reopens.

Summary

The Circuit Breaker pattern prevents continuous calls to a failing service, providing a graceful degradation (using the fallback method) while the service recovers. Using Resilience4j with Spring Boot simplifies the implementation, allowing you to control fault tolerance with minimal effort.

Java 17 LTS through the eyes of Scala developer

Introduction

Java and Scala are both modern, powerful languages that offer a range of features for developers, and while they differ in their paradigms (Java being primarily object-oriented and Scala being a hybrid of object-oriented and functional programming), they do share some similarities, especially as both languages have evolved. As an experienced Java developer (from Java 7 to the latest) and also experienced Scala developer I will try to compare the two languages.

1. Immutability

Immutability in Java refers to the design of objects whose state cannot be modified after they are created. Once an immutable object is instantiated, its fields cannot be changed, which provides several advantages, including thread safety, simplicity, and easier debugging. The most well-known immutable class in Java is String, but developers can create their own immutable classes.

Java 17

public record Person(String name, int age) {}

Records automatically generate:

  • constructor,
  • getters,
  • equals(),
  • hashCode(),
  • toString().

However, records provide shallow immutability.

record Team(List<String> members) {}

The reference cannot change, but the list can.

Scala 2.13

case class Person(name: String, age: Int)

Case classes are immutable by convention.

val p = Person("John", 30)

Fields are val unless explicitly declared var.

Scala 3

Same concept, with additional compiler improvements.

2. Sealed Types

Sealed types restrict who may extend a hierarchy.

Java 17

sealed interface Shape
    permits Circle, Rectangle {}

Useful for exhaustive pattern matching.

Scala 2.13

sealed trait Shape
case class Circle(r: Double) extends Shape
case class Rectangle(w: Double,h: Double) extends Shape

All subclasses must be in the same source file.

Scala 3

Exactly the same idea.

Scala 3 enums automatically create sealed hierarchies.

3. Pattern Matching

Pattern matching allows branching based on structure, not just type.

Java 17

if (obj instanceof String s) {
    System.out.println(s.length());
}

Switch pattern matching was still a preview feature.

Scala 2.13

shape match {
    case Circle(r) => ...
    case Rectangle(w,h) => ...
}

Can match:

  • values
  • types
  • tuples
  • lists
  • nested objects
  • regex
  • custom extractors

Scala’s pattern matching is far more expressive.

4. Type Inference

Type inference lets the compiler determine types automatically.

Java

var list = List.of(1,2,3);

Only local variables.

Scala

val list = List(1,2,3)

Inference works for

  • methods
  • lambdas
  • generic types
  • pattern matching
  • collections

Scala 3 further improves inference.

5. Lambdas

Lambdas represent anonymous functions.

Java

list.stream()
    .map(x -> x * 2)

Requires functional interfaces.

Scala

list.map(_ * 2)

Functions are true objects.

6. Collections

Collections are central to functional programming.

Java

Uses

  • List
  • Set
  • Map
  • Stream

Streams are separate from collections.

Scala

Everything supports

map
flatMap
filter
collect
groupBy
partition
fold
reduce
scan

without converting to streams.

7. Functional Programming

Functional programming emphasizes pure functions, immutable data, and function composition.

Java supports FP, but Scala is designed around it.

Scala libraries:

  • Cats
  • Cats Effect
  • ZIO
  • FS2

make purely functional programming practical.

8. Optional vs Option

Both represent optional values.

Java

Optional<String>

Designed mainly for return types.

Scala

Option[String]

Behaves like a collection.

option.map(...)
option.flatMap(...)
option.filter(...)

9. Null Safety

Null references are a common source of runtime errors.

Java

String s = null;

Perfectly legal.

Scala 2

Prefer

Option[String]

instead of null.

Scala 3

With Explicit Nulls

String

and

String | Null

are different types.

Much safer.

10. Exceptions

Exception handling reports errors during execution.

Java

  • checked exceptions
  • unchecked exceptions

Compiler forces checked exceptions.

Scala

Only unchecked exceptions.

Functional libraries usually avoid exceptions entirely by using:

  • Try
  • Either
  • IO
  • ZIO

11. Algebraic Data Types (ADT)

ADTs model complex domains by combining product types (AND) and sum types (OR).

Java

sealed interface Expr {}

record Num(int value) implements Expr {}
record Add(Expr l, Expr r) implements Expr {}

Scala

sealed trait Expr

case class Num(v:Int) extends Expr
case class Add(l:Expr,r:Expr) extends Expr

Scala 3

enum Expr:
  case Num(v:Int)
  case Add(l:Expr,r:Expr)

Scala offers much more concise ADT support.

12. Higher-Order Functions

Higher-order functions either accept functions as parameters or return functions.

Java:

list.forEach(System.out::println);

Scala:

list.foreach(println)

Higher-order functions are fundamental in Scala.

13. Implicits

Implicits allow values or conversions to be supplied automatically by the compiler.

Scala 2:

implicit val ec = ExecutionContext.global

Scala 3 replaces this with clearer syntax:

given ExecutionContext = ExecutionContext.global

def run(using ExecutionContext)

Java has no comparable language feature.

14. Extension Methods

Extension methods add behavior to existing types without inheritance.

Java uses utility classes:

Collections.sort(list);

Scala 2

implicit class RichString(s: String)

Scala 3

extension (s: String)
  def hello = ...

This is now a first-class language feature.

15. Value Classes

Value classes wrap existing types without (in many cases) introducing runtime allocation.

Scala 2:

class UserId(val value: Long) extends AnyVal

Scala 3 favors opaque types:

opaque type UserId = Long

These provide type safety with effectively zero runtime overhead.

Java has no direct equivalent in Java 17 (Project Valhalla aims to introduce value objects in a future release).

16. Type System

A language’s type system determines how expressive and safe programs can be.

Java offers:

  • generics
  • wildcards
  • bounded types
  • sealed types

Scala additionally supports:

  • higher-kinded types
  • variance annotations
  • path-dependent types
  • abstract type members
  • type classes
  • dependent function types (Scala 3)
  • union types (Scala 3)
  • intersection types (Scala 3)
  • match types (Scala 3)
  • opaque types (Scala 3)

Scala’s type system is among the most expressive of mainstream programming languages.

17. Compilation Speed

Compilation speed affects developer productivity, especially in large codebases.

  • Java 17: Fastest compilation due to a simpler type system and mature incremental compilation.
  • Scala 2.13: Noticeably slower because the compiler performs advanced type inference and implicit resolution.
  • Scala 3: Improves compiler architecture and is often faster than Scala 2.13, though still generally slower than Java for comparable projects.

18. Learning Curve

The learning curve reflects how quickly developers become productive.

  • Java 17: Gentle learning curve. A developer can become productive quickly thanks to a relatively small language surface and consistent syntax.
  • Scala 2.13: Steep learning curve. Besides object-oriented programming, developers must understand functional programming concepts, advanced collections, implicits, and a sophisticated type system.
  • Scala 3: Still challenging, but easier than Scala 2.13. The redesigned syntax, given/using mechanism, extension methods, enums, and improved error messages reduce much of the accidental complexity while preserving Scala’s expressive power.

Overall comparison

  • Choose Java 17 if you value simplicity, fast compilation, broad industry adoption, and ease of maintenance.
  • Choose Scala 2.13 if you work on existing Scala ecosystems (e.g., Spark, Akka Classic, Play Framework) and need maximum compatibility.
  • Choose Scala 3 if you want the full expressive power of Scala with a cleaner language design, stronger type safety, modern features such as enums, opaque types, union types, and native extension methods, and are starting a new Scala project.