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:
@ImportHttpServices: The new standard for communication between microservices.@Retryable& Co.: Resilience directly in the business logic.- 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.
![[EN] Spring Boot 4 Vertieft: Code-Beispiele für Declarative Clients, Resilience und Null Safety](/images/SpringBoot4DeepDive_BlogHeader.png)