API KVK avec Java et Spring Boot : données d'entreprises
Intégrez l'API KVKBase dans votre projet Java Spring Boot : RestClient, gestion des exceptions, cache et classe de service réutilisable.
Java reste l’un des langages dominants du logiciel d’entreprise. Des grandes boutiques en ligne aux plateformes fintech et aux systèmes RH, Java et Spring Boot forment l’ossature de nombreux systèmes critiques. Si vous travaillez dans un tel environnement et avez besoin des données d’entreprises néerlandaises issues de la Chambre de commerce, ce guide vous montre comment intégrer proprement l’API KVKBase avec les patterns modernes de Spring Boot.
Prérequis
- Java 21 ou supérieur (LTS)
- Spring Boot 3.3+
- Maven ou Gradle
- Une clé d’API KVKBase
Ajoutez le starter Spring Web à votre projet s’il n’y figure pas déjà :
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Étape 1 : configuration
Ajoutez votre clé d’API dans application.yml ou application.properties. N’écrivez jamais la clé en dur dans votre code source.
# application.yml
kvkbase:
api-key: ${KVKBASE_API_KEY}
base-url: https://api.kvkbase.nl/api/v1
timeout-ms: 5000
Créez ensuite une classe de propriétés de configuration :
// KvkbaseProperties.java
package com.yourcompany.kvk.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "kvkbase")
public class KvkbaseProperties {
private String apiKey;
private String baseUrl;
private int timeoutMs = 5000;
// Accesseurs (getters et setters)
public String getApiKey() { return apiKey; }
public void setApiKey(String apiKey) { this.apiKey = apiKey; }
public String getBaseUrl() { return baseUrl; }
public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
public int getTimeoutMs() { return timeoutMs; }
public void setTimeoutMs(int timeoutMs) { this.timeoutMs = timeoutMs; }
}
Étape 2 : classes de données (records)
Utilisez des records Java pour les réponses de l’API. Ils sont immuables, concis et bien adaptés à la lecture de données JSON.
// KvkCompany.java
package com.yourcompany.kvk.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public record KvkCompany(
String kvkNummer,
String naam,
String rechtsvorm,
boolean actief,
String startdatum,
String sbiCode,
String sbiOmschrijving,
KvkAddress address
) {}
// KvkAddress.java
package com.yourcompany.kvk.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public record KvkAddress(
String straat,
String huisnummer,
String postcode,
String plaats,
String land
) {}
Étape 3 : configurer RestClient
Spring Boot 3.2+ inclut le nouveau RestClient, qui remplace RestTemplate. Configurez un bean avec les bons en-têtes et le bon timeout :
// KvkbaseClientConfig.java
package com.yourcompany.kvk.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
@Configuration
public class KvkbaseClientConfig {
@Bean
public RestClient kvkbaseRestClient(KvkbaseProperties props) {
var factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(props.getTimeoutMs());
factory.setReadTimeout(props.getTimeoutMs());
return RestClient.builder()
.baseUrl(props.getBaseUrl())
.defaultHeader("x-api-key", props.getApiKey())
.defaultHeader("Accept", "application/json")
.requestFactory(factory)
.build();
}
}
Étape 4 : KvkbaseService
La classe de service contient toute la logique métier autour des recherches KVK. Centralisez-y la gestion des erreurs afin que le reste de votre application n’ait pas à se soucier des détails HTTP.
// KvkbaseService.java
package com.yourcompany.kvk.service;
import com.yourcompany.kvk.exception.CompanyNotFoundException;
import com.yourcompany.kvk.exception.KvkbaseApiException;
import com.yourcompany.kvk.model.KvkCompany;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatusCode;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
@Service
public class KvkbaseService {
private static final Logger log = LoggerFactory.getLogger(KvkbaseService.class);
private final RestClient restClient;
public KvkbaseService(RestClient kvkbaseRestClient) {
this.restClient = kvkbaseRestClient;
}
/**
* Récupère les données d'une entreprise à partir de son numéro KVK.
*
* @throws CompanyNotFoundException si le numéro KVK n'existe pas
* @throws KvkbaseApiException pour les autres erreurs d'API
*/
public KvkCompany getByKvkNumber(String kvkNumber) {
log.debug("KVK lookup for number: {}", kvkNumber);
return restClient.get()
.uri("/kvk/{kvkNumber}", kvkNumber)
.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
if (response.getStatusCode().value() == 404) {
throw new CompanyNotFoundException(kvkNumber);
}
throw new KvkbaseApiException(
"Client error during KVK lookup: " + response.getStatusCode()
);
})
.onStatus(HttpStatusCode::is5xxServerError, (request, response) -> {
throw new KvkbaseApiException(
"Server error during KVK lookup: " + response.getStatusCode()
);
})
.body(KvkCompany.class);
}
/**
* Vérifie si une entreprise est activement immatriculée.
* Renvoie false pour les numéros KVK inconnus.
*/
public boolean isActivelyRegistered(String kvkNumber) {
try {
KvkCompany company = getByKvkNumber(kvkNumber);
return company.actief();
} catch (CompanyNotFoundException e) {
return false;
}
}
}
Étape 5 : exceptions personnalisées
Créez des classes d’exception distinctes pour pouvoir différencier « introuvable » et « erreur d’API » en aval :
// CompanyNotFoundException.java
package com.yourcompany.kvk.exception;
public class CompanyNotFoundException extends RuntimeException {
private final String kvkNumber;
public CompanyNotFoundException(String kvkNumber) {
super("No company found with KVK number: " + kvkNumber);
this.kvkNumber = kvkNumber;
}
public String getKvkNumber() { return kvkNumber; }
}
// KvkbaseApiException.java
package com.yourcompany.kvk.exception;
public class KvkbaseApiException extends RuntimeException {
public KvkbaseApiException(String message) {
super(message);
}
}
Étape 6 : mise en cache avec Spring Cache
Les données KVK changent rarement. Mettez les résultats en cache localement pour limiter les appels d’API et garder une latence faible. Spring Cache convient bien à ce schéma.
Ajoutez le starter de cache :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Facultatif : Caffeine pour un cache LRU en mémoire -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
Activez le cache et configurez la durée de vie (TTL) :
// CacheConfig.java
package com.yourcompany.kvk.config;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager manager = new CaffeineCacheManager("kvkCompanies");
manager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(6, TimeUnit.HOURS)
.maximumSize(10_000));
return manager;
}
}
Annotez la méthode du service avec @Cacheable :
@Cacheable(value = "kvkCompanies", key = "#kvkNumber")
public KvkCompany getByKvkNumber(String kvkNumber) {
// ... implémentation existante
}
Un numéro KVK n’est désormais interrogé qu’une fois toutes les 6 heures au maximum. Un bon équilibre entre fraîcheur et efficacité.
Étape 7 : contrôleur REST
Si vous voulez exposer un endpoint d’API interne pour que le front-end ou d’autres services puissent récupérer les données d’entreprise :
// KvkController.java
package com.yourcompany.kvk.controller;
import com.yourcompany.kvk.exception.CompanyNotFoundException;
import com.yourcompany.kvk.model.KvkCompany;
import com.yourcompany.kvk.service.KvkbaseService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/companies")
public class KvkController {
private final KvkbaseService kvkbaseService;
public KvkController(KvkbaseService kvkbaseService) {
this.kvkbaseService = kvkbaseService;
}
@GetMapping("/{kvkNumber}")
public ResponseEntity<KvkCompany> getCompany(@PathVariable String kvkNumber) {
try {
KvkCompany company = kvkbaseService.getByKvkNumber(kvkNumber);
return ResponseEntity.ok(company);
} catch (CompanyNotFoundException e) {
return ResponseEntity.notFound().build();
}
}
@GetMapping("/{kvkNumber}/active")
public ResponseEntity<Boolean> isActive(@PathVariable String kvkNumber) {
boolean active = kvkbaseService.isActivelyRegistered(kvkNumber);
return ResponseEntity.ok(active);
}
}
Étape 8 : tests unitaires avec MockRestServiceServer
Testez la classe de service sans véritables appels HTTP grâce à MockRestServiceServer :
// KvkbaseServiceTest.java
package com.yourcompany.kvk.service;
import com.yourcompany.kvk.exception.CompanyNotFoundException;
import com.yourcompany.kvk.model.KvkCompany;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.*;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.*;
import static org.springframework.test.web.client.response.MockRestResponseCreators.*;
@SpringBootTest
class KvkbaseServiceTest {
@Autowired
private KvkbaseService service;
@Autowired
private RestClient kvkbaseRestClient;
@Test
void getByKvkNumber_returnsCompany() {
MockRestServiceServer server = MockRestServiceServer.bindTo(kvkbaseRestClient).build();
server.expect(requestTo("https://api.kvkbase.nl/api/v1/kvk/12345678"))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess("""
{
"kvkNummer": "12345678",
"naam": "Test BV",
"rechtsvorm": "Besloten Vennootschap",
"actief": true,
"startdatum": "2010-01-01"
}
""", MediaType.APPLICATION_JSON));
KvkCompany company = service.getByKvkNumber("12345678");
assertThat(company.naam()).isEqualTo("Test BV");
assertThat(company.actief()).isTrue();
}
@Test
void getByKvkNumber_throwsExceptionWhenNotFound() {
MockRestServiceServer server = MockRestServiceServer.bindTo(kvkbaseRestClient).build();
server.expect(requestTo("https://api.kvkbase.nl/api/v1/kvk/99999999"))
.andRespond(withStatus(HttpStatus.NOT_FOUND));
assertThatThrownBy(() -> service.getByKvkNumber("99999999"))
.isInstanceOf(CompanyNotFoundException.class);
}
}
Scénario réel : validation KVK lors du traitement d’une commande
Un cas d’usage courant : vérifier, lors de la création d’une commande B2B, que le client possède un numéro KVK actif.
@Service
public class OrderService {
private final KvkbaseService kvkbaseService;
private final OrderRepository orderRepository;
public OrderService(KvkbaseService kvkbaseService, OrderRepository orderRepository) {
this.kvkbaseService = kvkbaseService;
this.orderRepository = orderRepository;
}
public Order createOrder(OrderRequest request) {
// Vérifier le KVK avant de créer la commande
if (request.kvkNumber() != null) {
boolean active = kvkbaseService.isActivelyRegistered(request.kvkNumber());
if (!active) {
throw new InvalidCompanyException(
"KVK number " + request.kvkNumber() + " is not active"
);
}
// Enrichir la commande avec le nom de l'entreprise
KvkCompany company = kvkbaseService.getByKvkNumber(request.kvkNumber());
return orderRepository.save(new Order(
request.kvkNumber(),
company.naam(),
request.products()
));
}
return orderRepository.save(new Order(null, null, request.products()));
}
}
Résumé
Avec quelques composants Spring Boot, vous disposez d’une intégration KVK robuste :
KvkbasePropertiespour la configuration viaapplication.yml- Un bean
RestClientavec clé d’API et timeout KvkbaseServiceavec gestion centralisée des erreurs@Cacheablepour réutiliser efficacement les résultats- Des exceptions personnalisées pour distinguer clairement les erreurs
- Des tests unitaires via
MockRestServiceServer
Pour aller plus loin : API KVK avec Node.js et TypeScript pour une approche similaire dans les environnements JavaScript, et Données d’entreprise pour votre boutique en ligne pour des intégrations de paiement spécifiquement destinées au e-commerce.