[EN] Spring Boot 4 Vertieft: Code-Beispiele für Declarative Clients, Resilience und Null Safety

Julian | Oct 17, 2025 min read

Hello everyone,

The move to Spring Boot 4 is more than just a dependency update - it is a leap into a new era of application development. The base is Java 17+ and Jakarta EE 11, but the real gains lie in the Developer Experience (DX) and the reduction of boilerplate code.

The focus today is on three areas that make our code cleaner, more robust and more efficient:

  1. @ImportHttpServices: The new standard for communication between microservices.
  2. @Retryable & Co.: Resilience directly in the business logic.
  3. Null Safety with JSpecify: Finally fewer NullPointerException.

1. Declarative HTTP Clients mit @ImportHttpServices

Communication between microservices is everyday life. Spring Boot 4 / Spring Framework 7 standardizes this with the HTTP Interface Client, which eliminates the need for external libraries.

The elegant solution (@ImportHttpServices):

We simply define an interface and Spring generates the implementation at runtime.

Step 1: Define the interface We use HttpExchange and its variants like @GetExchange from the Spring Framework to set the API contract.

// src/main/java/com/example/client/ExternalServiceClient.java

import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.HttpExchange;

@HttpExchange(url = "/api/v1/data")
public interface ExternalServiceClient {

    record DataResponse(String id, String content) {}

    // Fuehrt einen GET-Aufruf auf /api/v1/data/{id} aus
    @GetExchange("/{id}")
    DataResponse fetchById(String id);
}

Step 2: Import the client The @ImportHttpServices annotation in a configuration class tells Spring which interfaces should be implemented as clients.

// src/main/java/com/example/config/ClientConfig.java

import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.support.ImportHttpServices;

@Configuration
@ImportHttpServices(
    // Definiert die Basis-URL und den zu nutzenden Client (RestClient oder WebClient)
    baseUrl = "${service.external.url}",
    types = {ExternalServiceClient.class}
)
public class ClientConfig {
    // Spring Boot 4 auto-konfiguriert den RestClient/WebClient Builder
}

The advantage: You simply inject the ExternalServiceClient into your service (@Autowired) as if it were a local bean and call the methods.


2. Built-in resilience with @Retryable and @ConcurrencyLimit

Microservices must be robust. Temporary network errors or dependency timeouts are commonplace. Spring Framework 7 offers built-in resilience features that make external libraries unnecessary in many simple cases.

The robust solution (@Retryable):

We define the retry behavior directly on the method, which is prone to temporary errors.

Step 1: Activate Resilience Skill

We add the annotation to the main class:

@SpringBootApplication
@EnableResilientMethods // Neue, explizite Aktivierung fuer Resilienz
public class DemoApplication {
    // ...
}

Step 2: Decorate the Logic

We use @Retryable and the associated @Recover for fallback. Additionally, we can use @ConcurrencyLimit to limit the maximum number of simultaneous calls and thus prevent overloading downstream services.

import org.springframework.resilience.annotation.ConcurrencyLimit;
import org.springframework.resilience.annotation.Backoff;
import org.springframework.resilience.annotation.Recover;
import org.springframework.resilience.annotation.Retryable;
import org.springframework.stereotype.Service;

@Service
public class PaymentService {

    // Simuliert eine Exception
    public static class RemoteServiceException extends RuntimeException {}

    @Retryable(
        retryFor = RemoteServiceException.class,
        maxAttempts = 3,
        backoff = @Backoff(delay = 500, multiplier = 1.5) // 0.5s, 0.75s, 1.125s
    )
    @ConcurrencyLimit(limit = 10) // Nur 10 gleichzeitige Zahlungen erlauben
    public String executePayment(String transactionId) throws RemoteServiceException {
        System.out.println("Zahlung wird versucht fuer ID: " + transactionId);
        // ... Logik
        return "SUCCESS"; 
    }

    // Fallback-Methode
    @Recover
    public String recoverPayment(RemoteServiceException e, String transactionId) {
        System.out.println("Alle Retries fehlgeschlagen. Fallback-Logik wird ausgefuehrt.");
        return "FALLBACK_TO_QUEUE";
    }
}

3. Null Safety mit JSpecify

Spring Framework 7 lays the foundation for reducing NullPointerException across the ecosystem by supporting the JSpecify specification. JSpecify is an attempt to define null security in Java through standard annotations.

The problem before:

We had to use custom or framework-specific @NonNull annotations. The zero expectation was often not clear and led to runtime errors.

The future solution (JSpecify):

Spring supports these standard annotations to clearly communicate intent. IDEs and static analysis tools can use these annotations to warn or report errors at compile time when a potentially null value is used where a non-null value is expected.

// Beispiel, wie JSpecify Annotationen in Java aussehen:

import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;

@Service
public class UserService {

    // Die Methode garantiert, dass der zurueckgegebene Wert NICHT null ist.
    public @NonNull User findUserById(String id) {
        // ... Logik
        return new User(id);
    }

    // Die Methode gibt einen Wert zurueck, der null sein KANN (z.B. wenn kein User gefunden wird).
    public @Nullable User findByEmail(String email) {
        // ... Logik
        return null; // Koennte null zurueckgeben
    }
}

The Benefit: By adopting these standards across the Spring ecosystem, the code we write and the libraries we use will be inherently more secure and reduce annoying NPEs before they even reach production.


Conclusion

Spring Boot 4 is the logical next step in the evolution of cloud native development. The integration of Declarative Clients and built-in resilience features eliminates boilerplate, while working on Null Safety makes our code safer. These innovations enable us to build leaner, more robust and more efficient microservices that are perfectly tailored to the modern Java platform.