Introduction à RestTemplate

Qu'est-ce que RestTemplate ?

Spring RestTemplate est un client HTTP synchrone qui simplifie la communication avec des services REST. C'est l'outil idéal pour qu'un microservice communique avec un autre service.

graph LR A[Microservice A
Client RestTemplate] --> B[RestTemplate] B --> C[Service B
Users API] B --> D[Service C
Products API] style A fill:#FF9800,stroke:#E65100 style B fill:#4CAF50,stroke:#388E3C style C fill:#2196F3,stroke:#0D47A1 style D fill:#9C27B0,stroke:#4A148C

Avantages de RestTemplate :

  • Simplicité : Méthodes simples pour toutes les opérations HTTP
  • Intégration Spring : Fonctionne parfaitement avec l'écosystème Spring
  • Sérialisation automatique : Conversion JSON ↔ Objets Java automatique
  • Gestion d'erreurs : Gestion intégrée des exceptions HTTP

Architecture des Microservices

Structure de l'application

graph TD A[Eureka Server
Port: 8761] --> B[Microservice A
Client - Port: 8080] A --> C[Microservice B
Users - Port: 8081] A --> D[Microservice C
Products - Port: 8082] B -- RestTemplate --> C B -- RestTemplate --> D style A fill:#2196F3,stroke:#0D47A1 style B fill:#FF9800,stroke:#E65100 style C fill:#4CAF50,stroke:#388E3C style D fill:#9C27B0,stroke:#4A148C

Rôles des microservices :

  • Microservice A : Client qui utilise RestTemplate pour communiquer
  • Microservice B : Service de gestion des utilisateurs
  • Microservice C : Service de gestion des produits
  • Eureka Server : Serveur de découverte de services

Microservice B - Service Utilisateurs (Simple)

1 Structure du projet

Arborescence du projet :

microserviceB/
├── pom.xml
└── src/main/
    ├── java/com/example/users/
    │   ├── UsersMicroserviceApplication.java
    │   ├── controller/
    │   │   └── UserController.java
    │   ├── model/
    │   │   └── User.java
    └── resources/
        └── application.properties

2 Fichier pom.xml

Dépendances Maven :

microserviceB/pom.xml
<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Eureka Client -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

3 Configuration application.properties

Fichier de configuration :

microserviceB/src/main/resources/application.properties
# === CONFIGURATION SERVEUR ===
server.port=8081
spring.application.name=service-b

# === CONFIGURATION EUREKA ===
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.instance.prefer-ip-address=true

4 Modèle User

Modèle User :

microserviceB/src/main/java/com/example/users/model/User.java
package com.example.users.model;

/**
 * Modèle représentant un utilisateur
 */
public class User {
    private Long id;
    private String name;
    private String email;
    private String department;
    
    // Constructeurs
 
    
    // Getters et Setters
   

5 Contrôleur REST

Contrôleur REST :

microserviceB/src/main/java/com/example/users/controller/UserController.java
package com.example.users.controller;

import com.example.users.model.User;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Contrôleur REST pour les opérations sur les utilisateurs
 */
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "*")
public class UserController {
    
    // Stockage en mémoire pour la démonstration
    private final ConcurrentHashMap<Long, User> users = new ConcurrentHashMap<>();
    private final AtomicLong counter = new AtomicLong();
    
    // Initialisation avec quelques utilisateurs
    public UserController() {
        users.put(counter.incrementAndGet(), new User(1L, "John Doe", "john@example.com", "IT"));
        users.put(counter.incrementAndGet(), new User(2L, "Jane Smith", "jane@example.com", "HR"));
        users.put(counter.incrementAndGet(), new User(3L, "Bob Johnson", "bob@example.com", "Finance"));
        counter.set(3);
    }
    
    /**
     * Récupère tous les utilisateurs
     * @return Liste d'utilisateurs
     */
    @GetMapping
    public List<User> getAllUsers() {
        return new ArrayList<>(users.values());
    }
    
    /**
     * Récupère un utilisateur par son ID
     * @param id ID de l'utilisateur
     * @return Utilisateur trouvé
     */
    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return users.get(id);
    }
    
    /**
     * Crée un nouvel utilisateur
     * @param user Utilisateur à créer
     * @return Utilisateur créé
     */
    @PostMapping
    public User createUser(@RequestBody User user) {
        Long id = counter.incrementAndGet();
        user.setId(id);
        users.put(id, user);
        return user;
    }
    
    /**
     * Met à jour un utilisateur existant
     * @param id ID de l'utilisateur
     * @param user Données de mise à jour
     * @return Utilisateur mis à jour
     */
    @PutMapping("/{id}")
    public User updateUser(@PathVariable Long id, @RequestBody User user) {
        user.setId(id);
        users.put(id, user);
        return user;
    }
    
    /**
     * Supprime un utilisateur
     * @param id ID de l'utilisateur à supprimer
     */
    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        users.remove(id);
    }
    
    /**
     * Recherche par département
     */
    @GetMapping(params = "department")
    public List<User> getUsersByDepartment(@RequestParam String department) {
        List<User> result = new ArrayList<>();
        for (User user : users.values()) {
            if (user.getDepartment().equals(department)) {
                result.add(user);
            }
        }
        return result;
    }
}

6 Classe principale

Application Spring Boot :

microserviceB/src/main/java/com/example/users/UsersMicroserviceApplication.java
package com.example.users;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * Classe principale du microservice de gestion des utilisateurs
 */
@SpringBootApplication
@EnableDiscoveryClient
public class UsersMicroserviceApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(UsersMicroserviceApplication.class, args);
    }
}

Microservice C - Service Produits (Simple)

1 Structure du projet

Arborescence du projet :

microserviceC/
├── pom.xml
└── src/main/
    ├── java/com/example/products/
    │   ├── ProductsMicroserviceApplication.java
    │   ├── controller/
    │   │   └── ProductController.java
    │   ├── model/
    │   │   └── Product.java
    └── resources/
        └── application.properties

2 Fichier pom.xml

Dépendances Maven :

microserviceC/pom.xml
<dependencies>
    <!-- Spring Boot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Eureka Client -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
</dependencies>

3 Configuration application.properties

Fichier de configuration :

microserviceC/src/main/resources/application.properties
# === CONFIGURATION SERVEUR ===
server.port=8082
spring.application.name=service-c

# === CONFIGURATION EUREKA ===
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.instance.prefer-ip-address=true

4 Modèle Product

Modèle Product :

microserviceC/src/main/java/com/example/products/model/Product.java
package com.example.products.model;

/**
 * Modèle représentant un produit
 */
public class Product {
    private Long id;
    private String name;
    private String description;
    private Double price;
    private String category;
    
    // Constructeurs
 
    
    // Getters et Setters
    

5 Contrôleur REST

Contrôleur REST :

microserviceC/src/main/java/com/example/products/controller/ProductController.java
package com.example.products.controller;

import com.example.products.model.Product;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Contrôleur REST pour les opérations sur les produits
 */
@RestController
@RequestMapping("/api/products")
@CrossOrigin(origins = "*")
public class ProductController {
    
    // Stockage en mémoire pour la démonstration
    private final ConcurrentHashMap<Long, Product> products = new ConcurrentHashMap<>();
    private final AtomicLong counter = new AtomicLong();
    
    // Initialisation avec quelques produits
    public ProductController() {
        products.put(counter.incrementAndGet(), new Product(1L, "Laptop", "High-performance laptop", 999.99, "Electronics"));
        products.put(counter.incrementAndGet(), new Product(2L, "Smartphone", "Latest smartphone model", 699.99, "Electronics"));
        products.put(counter.incrementAndGet(), new Product(3L, "Coffee Mug", "Ceramic coffee mug", 12.99, "Kitchen"));
        counter.set(3);
    }
    
    /**
     * Récupère tous les produits
     * @return Liste de produits
     */
    @GetMapping
    public List<Product> getAllProducts() {
        return new ArrayList<>(products.values());
    }
    
    /**
     * Récupère un produit par son ID
     * @param id ID du produit
     * @return Produit trouvé
     */
    @GetMapping("/{id}")
    public Product getProductById(@PathVariable Long id) {
        return products.get(id);
    }
    
    /**
     * Crée un nouveau produit
     * @param product Produit à créer
     * @return Produit créé
     */
    @PostMapping
    public Product createProduct(@RequestBody Product product) {
        Long id = counter.incrementAndGet();
        product.setId(id);
        products.put(id, product);
        return product;
    }
    
    /**
     * Met à jour un produit existant
     * @param id ID du produit
     * @param product Données de mise à jour
     * @return Produit mis à jour
     */
    @PutMapping("/{id}")
    public Product updateProduct(@PathVariable Long id, @RequestBody Product product) {
        product.setId(id);
        products.put(id, product);
        return product;
    }
    
    /**
     * Supprime un produit
     * @param id ID du produit à supprimer
     */
    @DeleteMapping("/{id}")
    public void deleteProduct(@PathVariable Long id) {
        products.remove(id);
    }
    
    /**
     * Recherche par catégorie
     */
    @GetMapping(params = "category")
    public List<Product> getProductsByCategory(@RequestParam String category) {
        List<Product> result = new ArrayList<>();
        for (Product product : products.values()) {
            if (product.getCategory().equals(category)) {
                result.add(product);
            }
        }
        return result;
    }
    
    /**
     * Recherche par prix maximum
     */
    @GetMapping(params = "maxPrice")
    public List<Product> getProductsByMaxPrice(@RequestParam Double maxPrice) {
        List<Product> result = new ArrayList<>();
        for (Product product : products.values()) {
            if (product.getPrice() <= maxPrice) {
                result.add(product);
            }
        }
        return result;
    }
}

6 Classe principale

Application Spring Boot :

microserviceC/src/main/java/com/example/products/ProductsMicroserviceApplication.java
package com.example.products;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * Classe principale du microservice de gestion des produits
 */
@SpringBootApplication
@EnableDiscoveryClient
public class ProductsMicroserviceApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(ProductsMicroserviceApplication.class, args);
    }
}

Microservice A - Client RestTemplate (Fonctionnalités Avancées)

1 Configuration de Base de RestTemplate

Configuration Spring Boot :

microserviceA/src/main/java/com/example/client/config/RestTemplateConfig.java
package com.example.client.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import java.nio.charset.StandardCharsets;

/**
 * Configuration de base de RestTemplate
 */
@Configuration
public class RestTemplateConfig {
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        
        // Configuration des convertisseurs de messages
        restTemplate.getMessageConverters().add(0, new StringHttpMessageConverter(StandardCharsets.UTF_8));
        
        return restTemplate;
    }
}

Explication détaillée :

RestTemplate est le client HTTP principal de Spring pour effectuer des appels REST. Il gère automatiquement la sérialisation/désérialisation JSON, les conversions de types, et fournit des méthodes simples pour toutes les opérations HTTP.

GetMapping : Utilisé pour les requêtes HTTP GET

PostMapping : Utilisé pour les requêtes HTTP POST

PutMapping : Utilisé pour les requêtes HTTP PUT

DeleteMapping : Utilisé pour les requêtes HTTP DELETE

2 Modèles de Données

microserviceA/src/main/java/com/example/client/model/User.java
package com.example.client.model;

// Même structure que dans le microservice B
public class User {
    private Long id;
    private String name;
    private String email;
    private String department;
    
    // Constructeurs, getters 

    
microserviceA/src/main/java/com/example/client/model/Product.java
package com.example.client.model;

// Même structure que dans le microservice C
public class Product {
    private Long id;
    private String name;
    private String description;
    private Double price;
    private String category;
    
    // Constructeurs, getters et setters 
  

3 Service RestTemplate - Fonctionnalités de Base

Service de communication avec méthodes de base :

microserviceA/src/main/java/com/example/client/service/BasicRestTemplateService.java
package com.example.client.service;

import com.example.client.model.User;
import com.example.client.model.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.Arrays;
import java.util.List;

/**
 * Service démontrant les fonctionnalités de base de RestTemplate
 */
@Service
public class BasicRestTemplateService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    // === MÉTHODES GET - Récupération de données ===
    
    /**
     * getForObject() - Méthode la plus simple pour récupérer un objet
     * Utilisée quand on ne besoin que du corps de la réponse
     */
    public User getUserById(Long id) {
        String url = "http://service-b/api/users/" + id;
        // getForObject(url, typeDeRetour)
        return restTemplate.getForObject(url, User.class);
    }
    
    /**
     * getForEntity() - Récupère l'objet ET les métadonnées (headers, status code)
     * Utilisée quand on a besoin d'informations supplémentaires
     */
    public ResponseEntity<User> getUserWithDetails(Long id) {
        String url = "http://service-b/api/users/" + id;
        // getForEntity(url, typeDeRetour) - retourne ResponseEntity
        return restTemplate.getForEntity(url, User.class);
    }
    
    /**
     * Récupération d'une liste d'objets
     * getForObject ne gère pas directement List, donc on utilise un tableau
     */
    public List<User> getAllUsers() {
        String url = "http://service-b/api/users";
        // On récupère un tableau et on le convertit en liste
        User[] usersArray = restTemplate.getForObject(url, User[].class);
        return Arrays.asList(usersArray);
    }
    
    /**
     * Récupération avec paramètres de requête
     * Utilisation de UriComponentsBuilder pour construire l'URL
     */
    public List<User> getUsersByDepartment(String department) {
        // Construction sécurisée de l'URL avec paramètres
        String url = UriComponentsBuilder.fromHttpUrl("http://service-b/api/users")
                .queryParam("department", department)
                .toUriString();
        
        User[] usersArray = restTemplate.getForObject(url, User[].class);
        return Arrays.asList(usersArray);
    }
    
    // === MÉTHODES POST - Création de ressources ===
    
    /**
     * postForObject() - Crée une ressource et retourne l'objet créé
     * La sérialisation JSON est automatique
     */
    public User createUser(User user) {
        String url = "http://service-b/api/users";
        // postForObject(url, objetAEnvoyer, typeDeRetour)
        return restTemplate.postForObject(url, user, User.class);
    }
    
    /**
     * postForEntity() - Crée une ressource et retourne ResponseEntity
     * Utile pour accéder aux headers et au code status
     */
    public ResponseEntity<User> createUserWithResponse(User user) {
        String url = "http://service-b/api/users";
        // postForEntity(url, objetAEnvoyer, typeDeRetour)
        return restTemplate.postForEntity(url, user, User.class);
    }
    
    /**
     * postForLocation() - Crée une ressource et retourne l'URI de la ressource créée
     * Utile pour obtenir l'URL de la nouvelle ressource
     */
    public URI createUserAndGetLocation(User user) {
        String url = "http://service-b/api/users";
        // postForLocation(url, objetAEnvoyer) - retourne URI
        return restTemplate.postForLocation(url, user);
    }
    
    // === MÉTHODES PUT - Mise à jour de ressources ===
    
    /**
     * put() - Met à jour une ressource
     * Ne retourne rien (void)
     */
    public void updateUser(Long id, User user) {
        String url = "http://service-b/api/users/" + id;
        // put(url, objetMisAJour)
        restTemplate.put(url, user);
    }
    
    // === MÉTHODES DELETE - Suppression de ressources ===
    
    /**
     * delete() - Supprime une ressource
     * Ne retourne rien (void)
     */
    public void deleteUser(Long id) {
        String url = "http://service-b/api/users/" + id;
        // delete(url)
        restTemplate.delete(url);
    }
    
    /**
     * delete() avec URI - Suppression avec construction d'URI
     */
    public void deleteUserWithUri(Long id) {
        // Construction d'URI avec variables
        String url = "http://service-b/api/users/{id}";
        // delete(url, variables...)
        restTemplate.delete(url, id);
    }
}

Explication des méthodes de base :

getForObject(url, responseType) : La méthode la plus simple pour récupérer un objet. Retourne directement l'objet désérialisé.

getForEntity(url, responseType) : Récupère un objet avec ses métadonnées (headers, status code). Retourne un ResponseEntity.

postForObject(url, request, responseType) : Crée une ressource et retourne l'objet créé. L'objet est automatiquement sérialisé en JSON.

postForEntity(url, request, responseType) : Crée une ressource et retourne ResponseEntity avec métadonnées.

postForLocation(url, request) : Crée une ressource et retourne l'URI de la nouvelle ressource.

put(url, request) : Met à jour une ressource. Ne retourne rien.

delete(url) : Supprime une ressource. Ne retourne rien.

6 Contrôleur REST Client

Contrôleur exposant les fonctionnalités :

microserviceA/src/main/java/com/example/client/controller/ClientController.java
package com.example.client.controller;

import com.example.client.model.User;
import com.example.client.model.Product;
import com.example.client.service.BasicRestTemplateService;
//import com.example.client.service.AdvancedRestTemplateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * Contrôleur REST pour exposer les fonctionnalités du client RestTemplate
 */
@RestController
@RequestMapping("/api/client")
@CrossOrigin(origins = "*")
public class ClientController {
    
    @Autowired
    private BasicRestTemplateService basicService;
   
    // === ENDPOINTS FONCTIONNALITÉS DE BASE ===
    
    @GetMapping("/users/{id}")
    public User getUserById(@PathVariable Long id) {
        return basicService.getUserById(id);
    }
    
    @GetMapping("/users/{id}/details")
    public ResponseEntity<User> getUserWithDetails(@PathVariable Long id) {
        return basicService.getUserWithDetails(id);
    }
    
    @GetMapping("/users")
    public List<User> getAllUsers() {
        return basicService.getAllUsers();
    }
    
    @GetMapping("/users/department/{department}")
    public List<User> getUsersByDepartment(@PathVariable String department) {
        return basicService.getUsersByDepartment(department);
    }
    
    @PostMapping("/users")
    public User createUser(@RequestBody User user) {
        return basicService.createUser(user);
    }
    
    @PostMapping("/users/with-response")
    public ResponseEntity<User> createUserWithResponse(@RequestBody User user) {
        return basicService.createUserWithResponse(user);
    }
    
    @PutMapping("/users/{id}")
    public void updateUser(@PathVariable Long id, @RequestBody User user) {
        basicService.updateUser(id, user);
    }
    
    @DeleteMapping("/users/{id}")
    public void deleteUser(@PathVariable Long id) {
        basicService.deleteUser(id);
    }
    
}

Tester les fonctionalities de base pusis passer aux fonctionalities AVANCÉES






4 Service RestTemplate - Fonctionnalités Avancées

Service avec méthodes avancées :

microserviceA/src/main/java/com/example/client/service/AdvancedRestTemplateService.java
package com.example.client.service;

import com.example.client.model.User;
import com.example.client.model.Product;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.List;
import java.util.Arrays;

/**
 * Service démontrant les fonctionnalités avancées de RestTemplate
 */
@Service
public class AdvancedRestTemplateService {
    
    @Autowired
    private RestTemplate restTemplate;
    
    // === UTILISATION DE HttpEntity ET HttpHeaders ===
    
    /**
     * Envoi personnalisé avec headers HTTP
     * HttpEntity permet d'envoyer des headers personnalisés avec le corps
     */
    public ResponseEntity<User> createUserWithCustomHeaders(User user) {
        String url = "http://service-b/api/users";
        
        // Création des headers personnalisés
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        headers.set("X-API-Key", "votre-cle-api");
        headers.set("User-Agent", "MonApplication/1.0");
        
        // Création de l'entité HTTP (headers + corps)
        HttpEntity<User> requestEntity = new HttpEntity<>(user, headers);
        
        // Envoi avec exchange() - méthode la plus flexible
        return restTemplate.exchange(
                url,                    // URL
                HttpMethod.POST,        // Méthode HTTP
                requestEntity,          // Entité HTTP (headers + corps)
                User.class              // Type de retour
        );
    }
    
    /**
     * Récupération avec headers personnalisés
     * Utile pour l'authentification, le cache, etc.
     */
    public ResponseEntity<List<User>> getAllUsersWithHeaders() {
        String url = "http://service-b/api/users";
        
        // Création des headers
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        headers.set("Authorization", "Bearer votre-token-jwt");
        headers.set("If-None-Match", "version-cache");
        
        // Entité HTTP avec headers seulement
        HttpEntity<String> entity = new HttpEntity<>(headers);
        
        // Utilisation de ParameterizedTypeReference pour List
        return restTemplate.exchange(
                url,
                HttpMethod.GET,
                entity,
                new ParameterizedTypeReference<List<User>>() {}
        );
    }
    
    // === GESTION DES TYPES COMPLEXES AVEC ParameterizedTypeReference ===
    
    /**
     * Récupération de listes complexes avec ParameterizedTypeReference
     * Nécessaire pour les types génériques comme List
     */
    public List<User> getUsersByDepartmentAdvanced(String department) {
        String url = UriComponentsBuilder.fromHttpUrl("http://service-b/api/users")
                .queryParam("department", department)
                .toUriString();
        
        // Headers personnalisés
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        
        HttpEntity<String> entity = new HttpEntity<>(headers);
        
        // exchange() avec ParameterizedTypeReference pour List
        ResponseEntity<List<User>> response = restTemplate.exchange(
                url,
                HttpMethod.GET,
                entity,
                new ParameterizedTypeReference<List<User>>() {}
        );
        
        return response.getBody();
    }
    
    // === GESTION DES ERREURS HTTP ===
    
    /**
     * Gestion robuste des erreurs HTTP
     * Capture des exceptions spécifiques aux erreurs client/serveur
     */
    public User getUserByIdWithErrorHandling(Long id) {
        String url = "http://service-b/api/users/" + id;
        
        try {
            return restTemplate.getForObject(url, User.class);
        } catch (HttpClientErrorException e) {
            // Erreurs 4xx (404 Not Found, 400 Bad Request, etc.)
            System.err.println("Erreur client HTTP: " + e.getStatusCode());
            System.err.println("Réponse: " + e.getResponseBodyAsString());
            throw new RuntimeException("Utilisateur non trouvé ou requête invalide", e);
        } catch (HttpServerErrorException e) {
            // Erreurs 5xx (500 Internal Server Error, 503 Service Unavailable, etc.)
            System.err.println("Erreur serveur HTTP: " + e.getStatusCode());
            System.err.println("Réponse: " + e.getResponseBodyAsString());
            throw new RuntimeException("Erreur du service utilisateur", e);
        } catch (Exception e) {
            // Autres erreurs (réseau, timeout, etc.)
            System.err.println("Erreur inattendue: " + e.getMessage());
            throw new RuntimeException("Impossible de contacter le service utilisateur", e);
        }
    }
    
    /**
     * Vérification du code status avant traitement
     * Approche proactive pour gérer les erreurs
     */
    public User getUserWithStatusCheck(Long id) {
        String url = "http://service-b/api/users/" + id;
        
        // Récupération avec ResponseEntity pour accéder au status
        ResponseEntity<User> response = restTemplate.getForEntity(url, User.class);
        
        // Vérification du code status
        if (response.getStatusCode().is2xxSuccessful()) {
            return response.getBody();
        } else if (response.getStatusCode().is4xxClientError()) {
            throw new RuntimeException("Erreur client: " + response.getStatusCode());
        } else if (response.getStatusCode().is5xxServerError()) {
            throw new RuntimeException("Erreur serveur: " + response.getStatusCode());
        } else {
            throw new RuntimeException("Code status inattendu: " + response.getStatusCode());
        }
    }
    
    // === UTILISATION DE exchange() - LA MÉTHODE ULTIME ===
    
    /**
     * exchange() - Méthode la plus puissante et flexible
     * Permet de tout contrôler : méthode HTTP, headers, corps, type de retour
     */
    public ResponseEntity<User> performCustomRequest(String method, String url, Object requestBody, Class<User> responseType) {
        HttpMethod httpMethod = HttpMethod.valueOf(method.toUpperCase());
        
        // Création des headers
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
        
        // Création de l'entité HTTP
        HttpEntity<Object> requestEntity = new HttpEntity<>(requestBody, headers);
        
        // Exécution de la requête personnalisée
        return restTemplate.exchange(
                url,
                httpMethod,
                requestEntity,
                responseType
        );
    }
    
    /**
     * exchange() avec requête PATCH (non supportée nativement par RestTemplate)
     */
    public ResponseEntity<User> updateUserPartially(Long id, User partialUser) {
        String url = "http://service-b/api/users/" + id;
        
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        
        HttpEntity<User> requestEntity = new HttpEntity<>(partialUser, headers);
        
        return restTemplate.exchange(
                url,
                HttpMethod.PATCH,  // PATCH supporté via exchange()
                requestEntity,
                User.class
        );
    }
    
    // === REQUÊTES AVEC VARIABLES D'URI ===
    
    /**
     * Utilisation de variables d'URI pour des URLs dynamiques
     * Plus sécurisé que la concaténation de chaînes
     */
    public User getUserByIdWithUriVariables(Long id) {
        String url = "http://service-b/api/users/{userId}";
        
        // Passage des variables par ordre
        return restTemplate.getForObject(url, User.class, id);
        
        // Ou passage des variables par nom
        // return restTemplate.getForObject(url, User.class, Collections.singletonMap("userId", id));
    }
    
    /**
     * Requête complexe avec plusieurs variables
     */
    public List<Product> searchProducts(String category, Double maxPrice) {
        String url = "http://service-c/api/products?category={category}&maxPrice={maxPrice}";
        
        // Passage des variables par ordre
        Product[] products = restTemplate.getForObject(
                url, 
                Product[].class, 
                category, 
                maxPrice
        );
        
        return Arrays.asList(products);
    }
}

Explication des fonctionnalités avancées :

HttpEntity<T> : Classe qui encapsule à la fois le corps de la requête et les headers HTTP. Permet un contrôle total sur les requêtes sortantes.

HttpHeaders : Classe pour manipuler les headers HTTP. Permet d'ajouter des headers personnalisés comme Authorization, Content-Type, Accept, etc.

exchange() : Méthode la plus flexible de RestTemplate. Permet de spécifier la méthode HTTP, les headers, le corps de la requête, et le type de retour. Supporte toutes les méthodes HTTP y compris PATCH.

ParameterizedTypeReference : Nécessaire pour désérialiser des types génériques comme List<User>. Contourne les limitations de l'effacement de type en Java.

Gestion des erreurs : Capture des exceptions spécifiques HttpClientErrorException (4xx) et HttpServerErrorException (5xx) pour une gestion précise des erreurs HTTP.

Variables d'URI : Utilisation de {variable} dans les URLs pour une construction sécurisée et éviter les injections.

5 Configuration Avancée de RestTemplate

Configuration avec timeout, pooling, etc. :

microserviceA/src/main/java/com/example/client/config/AdvancedRestTemplateConfig.java
package com.example.client.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

/**
 * Configuration avancée de RestTemplate avec timeout et pooling
 */
@Configuration
public class AdvancedRestTemplateConfig {
    
    @Bean
    public RestTemplate advancedRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        
        // Configuration avancée avec Apache HttpClient
        HttpComponentsClientHttpRequestFactory factory = 
            new HttpComponentsClientHttpRequestFactory();
        
        // Timeout de connexion (temps pour établir la connexion)
        factory.setConnectTimeout(5000);    // 5 secondes
        
        // Timeout de lecture (temps pour lire la réponse)
        factory.setReadTimeout(10000);      // 10 secondes
        
        // Timeout de demande (temps pour obtenir une connexion du pool)
        factory.setConnectionRequestTimeout(2000); // 2 secondes
        
        restTemplate.setRequestFactory(factory);
        
        return restTemplate;
    }
}
microserviceA/src/main/java/com/example/client/interceptor/LoggingInterceptor.java
package com.example.client.interceptor;

import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * Intercepteur pour logger les requêtes et réponses HTTP
 */
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
    
    @Override
    public ClientHttpResponse intercept(
            HttpRequest request,
            byte[] body,
            ClientHttpRequestExecution execution) throws IOException {
        
        // Log de la requête
        logRequest(request, body);
        
        // Exécution de la requête
        ClientHttpResponse response = execution.execute(request, body);
        
        // Log de la réponse
        logResponse(response);
        
        return response;
    }
    
    private void logRequest(HttpRequest request, byte[] body) {
        System.out.println("=== HTTP REQUEST ===");
        System.out.println("URI: " + request.getURI());
        System.out.println("Method: " + request.getMethod());
        System.out.println("Headers: " + request.getHeaders());
        System.out.println("Body: " + new String(body, StandardCharsets.UTF_8));
    }
    
    private void logResponse(ClientHttpResponse response) throws IOException {
        System.out.println("=== HTTP RESPONSE ===");
        System.out.println("Status: " + response.getStatusCode());
        System.out.println("Headers: " + response.getHeaders());
        System.out.println("Body: " + StreamUtils.copyToString(
                response.getBody(), StandardCharsets.UTF_8));
    }
}
microserviceA/src/main/java/com/example/client/config/InterceptedRestTemplateConfig.java
package com.example.client.config;

import com.example.client.interceptor.LoggingInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import java.util.Collections;

/**
 * Configuration de RestTemplate avec intercepteur
 */
@Configuration
public class InterceptedRestTemplateConfig {
    
	@LoadBalanced
    @Bean
    public RestTemplate interceptedRestTemplate() {
        RestTemplate restTemplate = new RestTemplate();
        
        // Ajout de l'intercepteur pour le logging
        restTemplate.setInterceptors(Collections.singletonList(new LoggingInterceptor()));
        
        return restTemplate;
    }
}

6 Contrôleur REST Client

Contrôleur exposant les fonctionnalités :

microserviceA/src/main/java/com/example/client/controller/ClientController.java
package com.example.client.controller;

import com.example.client.model.User;
import com.example.client.model.Product;
import com.example.client.service.AdvancedRestTemplateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * Contrôleur REST pour exposer les fonctionnalités du client RestTemplate
 */
@RestController
@RequestMapping("/api/client2")
@CrossOrigin(origins = "*")
public class ClientController {
    
 
    @Autowired
    private AdvancedRestTemplateService advancedService;
 
    // === ENDPOINTS FONCTIONNALITÉS DE BASE ===
   
    @GetMapping("/users/with-headers")
    public ResponseEntity<List<User>> getAllUsersWithHeaders() {
        return advancedService.getAllUsersWithHeaders();
    }
    
    @GetMapping("/users/department/{department}/advanced")
    public List<User> getUsersByDepartmentAdvanced(@PathVariable String department) {
        return advancedService.getUsersByDepartmentAdvanced(department);
    }
    
    @GetMapping("/users/{id}/safe")
    public User getUserByIdWithErrorHandling(@PathVariable Long id) {
        return advancedService.getUserByIdWithErrorHandling(id);
    }
    
    @GetMapping("/users/{id}/checked")
    public User getUserWithStatusCheck(@PathVariable Long id) {
        return advancedService.getUserWithStatusCheck(id);
    }
    
    @PatchMapping("/users/{id}")
    public ResponseEntity<User> updateUserPartially(@PathVariable Long id, @RequestBody User partialUser) {
        return advancedService.updateUserPartially(id, partialUser);
    }
    
    @GetMapping("/users/{id}/uri-variables")
    public User getUserByIdWithUriVariables(@PathVariable Long id) {
        return advancedService.getUserByIdWithUriVariables(id);
    }
	
}

Tableau Récapitulatif des Fonctionnalités

Fonctionnalité Méthode RestTemplate Utilisation Avantages
Récupération simple getForObject() Quand seul le corps de la réponse est nécessaire Simplicité, conversion automatique
Récupération avec métadonnées getForEntity() Quand headers/status code sont nécessaires Accès complet à la réponse HTTP
Création de ressource postForObject() Création et retour de l'objet créé Simplicité, sérialisation automatique
Création avec réponse détaillée postForEntity() Création avec accès aux headers Contrôle total sur la réponse
Obtention de l'URI créée postForLocation() Quand seule l'URI est nécessaire Performance optimale
Mise à jour put() Mise à jour de ressources existantes Simplicité, pas de retour nécessaire
Suppression delete() Suppression de ressources Simplicité, pas de retour nécessaire
Requêtes personnalisées exchange() Contrôle total sur la requête Flexibilité maximale, support de toutes les méthodes