KVK API met Java en Spring Boot: Bedrijfsgegevens opvragen in je Java-applicatie
KVKBase Team

KVK API met Java en Spring Boot: Bedrijfsgegevens opvragen in je Java-applicatie

Integreer de KVKBase API in je Java Spring Boot project. Met codevoorbeelden voor RestClient, exception handling, caching en een herbruikbare service-klasse, geschikt voor enterprise Java-developers.

kvkapijavaspring-bootdevelopersintegratie

Java blijft een van de dominante talen in enterprise software. Van grote webshops tot fintech-platforms en HR-systemen, Java en Spring Boot vormen de ruggengraat van veel kritische systemen in Nederland. Als je in zo’n omgeving werkt en bedrijfsgegevens uit de KVK nodig hebt, laten we in deze gids zien hoe je de KVKBase API netjes integreert met moderne Spring Boot-patronen.

Vereisten

  • Java 21 of hoger (LTS)
  • Spring Boot 3.3+
  • Maven of Gradle
  • Een API-key van KVKBase

Voeg de Spring Web starter toe aan je project als die er nog niet in zit:

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Stap 1: Configuratie

Voeg je API-key toe aan application.yml of application.properties. Zet de key nooit hardcoded in je code.

# application.yml
kvkbase:
  api-key: ${KVKBASE_API_KEY}
  base-url: https://api.kvkbase.nl/api/v1
  timeout-ms: 5000

Maak vervolgens een configuratieklasse aan:

// KvkbaseProperties.java
package nl.jouwbedrijf.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;

    // Getters en 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; }
}

Stap 2: Data-klassen (Records)

Gebruik Java records voor de API-responses. Ze zijn immutable, compact en passen goed bij het lezen van JSON-data.

// KvkBedrijf.java
package nl.jouwbedrijf.kvk.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public record KvkBedrijf(
    String kvkNummer,
    String naam,
    String rechtsvorm,
    boolean actief,
    String startdatum,
    String sbiCode,
    String sbiOmschrijving,
    KvkAdres adres
) {}
// KvkAdres.java
package nl.jouwbedrijf.kvk.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public record KvkAdres(
    String straat,
    String huisnummer,
    String postcode,
    String plaats,
    String land
) {}

Stap 3: RestClient configureren

Spring Boot 3.2+ heeft de nieuwe RestClient die RestTemplate vervangt. Configureer een bean met de juiste headers en timeout:

// KvkbaseClientConfig.java
package nl.jouwbedrijf.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();
    }
}

Stap 4: KvkbaseService

De service-klasse bevat alle businesslogica rondom KVK-ophalen. Centraliseer hier de foutafhandeling zodat de rest van je applicatie niet hoeft na te denken over HTTP-details.

// KvkbaseService.java
package nl.jouwbedrijf.kvk.service;

import nl.jouwbedrijf.kvk.exception.BedrijfNietGevondenException;
import nl.jouwbedrijf.kvk.exception.KvkbaseApiException;
import nl.jouwbedrijf.kvk.model.KvkBedrijf;
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;
    }

    /**
     * Haalt bedrijfsgegevens op via KVK-nummer.
     *
     * @throws BedrijfNietGevondenException als het KVK-nummer niet bestaat
     * @throws KvkbaseApiException bij overige API-fouten
     */
    public KvkBedrijf ophalenOpKvkNummer(String kvkNummer) {
        log.debug("KVK-opzoeking voor nummer: {}", kvkNummer);

        return restClient.get()
                .uri("/kvk/{kvkNummer}", kvkNummer)
                .retrieve()
                .onStatus(HttpStatusCode::is4xxClientError, (request, response) -> {
                    if (response.getStatusCode().value() == 404) {
                        throw new BedrijfNietGevondenException(kvkNummer);
                    }
                    throw new KvkbaseApiException(
                        "Client-fout bij KVK-opzoeking: " + response.getStatusCode()
                    );
                })
                .onStatus(HttpStatusCode::is5xxServerError, (request, response) -> {
                    throw new KvkbaseApiException(
                        "Server-fout bij KVK-opzoeking: " + response.getStatusCode()
                    );
                })
                .body(KvkBedrijf.class);
    }

    /**
     * Controleert of een bedrijf actief is ingeschreven.
     * Geeft false terug bij een onbekend KVK-nummer.
     */
    public boolean isActiefIngeschreven(String kvkNummer) {
        try {
            KvkBedrijf bedrijf = ophalenOpKvkNummer(kvkNummer);
            return bedrijf.actief();
        } catch (BedrijfNietGevondenException e) {
            return false;
        }
    }
}

Stap 5: Custom exceptions

Maak aparte exception-klassen zodat je stroomafwaarts onderscheid kunt maken tussen “niet gevonden” en “API-probleem”:

// BedrijfNietGevondenException.java
package nl.jouwbedrijf.kvk.exception;

public class BedrijfNietGevondenException extends RuntimeException {
    private final String kvkNummer;

    public BedrijfNietGevondenException(String kvkNummer) {
        super("Geen bedrijf gevonden met KVK-nummer: " + kvkNummer);
        this.kvkNummer = kvkNummer;
    }

    public String getKvkNummer() { return kvkNummer; }
}
// KvkbaseApiException.java
package nl.jouwbedrijf.kvk.exception;

public class KvkbaseApiException extends RuntimeException {
    public KvkbaseApiException(String message) {
        super(message);
    }
}

Stap 6: Caching met Spring Cache

KVK-data verandert zelden. Sla resultaten op in een lokale cache zodat je API-calls beperkt en latency laag blijft. Spring Cache werkt goed voor dit patroon.

Voeg de cache starter toe:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Optioneel: Caffeine voor een in-memory LRU-cache -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

Activeer caching en configureer TTL:

// CacheConfig.java
package nl.jouwbedrijf.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("kvkBedrijven");
        manager.setCaffeine(Caffeine.newBuilder()
                .expireAfterWrite(6, TimeUnit.HOURS)
                .maximumSize(10_000));
        return manager;
    }
}

Annoteer de service-methode met @Cacheable:

@Cacheable(value = "kvkBedrijven", key = "#kvkNummer")
public KvkBedrijf ophalenOpKvkNummer(String kvkNummer) {
    // ... bestaande implementatie
}

Nu wordt een KVK-nummer maximaal eens per 6 uur opgezocht bij de API. Perfecte balans tussen versheid en efficiency.

Stap 7: REST Controller

Stel dat je een interne API-endpoint wilt aanbieden zodat front-end of andere services bedrijfsdata kunnen ophalen:

// KvkController.java
package nl.jouwbedrijf.kvk.controller;

import nl.jouwbedrijf.kvk.exception.BedrijfNietGevondenException;
import nl.jouwbedrijf.kvk.model.KvkBedrijf;
import nl.jouwbedrijf.kvk.service.KvkbaseService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/bedrijven")
public class KvkController {

    private final KvkbaseService kvkbaseService;

    public KvkController(KvkbaseService kvkbaseService) {
        this.kvkbaseService = kvkbaseService;
    }

    @GetMapping("/{kvkNummer}")
    public ResponseEntity<KvkBedrijf> getBedrijf(@PathVariable String kvkNummer) {
        try {
            KvkBedrijf bedrijf = kvkbaseService.ophalenOpKvkNummer(kvkNummer);
            return ResponseEntity.ok(bedrijf);
        } catch (BedrijfNietGevondenException e) {
            return ResponseEntity.notFound().build();
        }
    }

    @GetMapping("/{kvkNummer}/actief")
    public ResponseEntity<Boolean> isActief(@PathVariable String kvkNummer) {
        boolean actief = kvkbaseService.isActiefIngeschreven(kvkNummer);
        return ResponseEntity.ok(actief);
    }
}

Stap 8: Unit tests met MockRestServiceServer

Test de service-klasse zonder echte HTTP-calls via MockRestServiceServer:

// KvkbaseServiceTest.java
package nl.jouwbedrijf.kvk.service;

import nl.jouwbedrijf.kvk.exception.BedrijfNietGevondenException;
import nl.jouwbedrijf.kvk.model.KvkBedrijf;
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 ophalenOpKvkNummer_geeftBedrijfTerug() {
        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));

        KvkBedrijf bedrijf = service.ophalenOpKvkNummer("12345678");

        assertThat(bedrijf.naam()).isEqualTo("Test BV");
        assertThat(bedrijf.actief()).isTrue();
    }

    @Test
    void ophalenOpKvkNummer_gooidExceptionBijNietGevonden() {
        MockRestServiceServer server = MockRestServiceServer.bindTo(kvkbaseRestClient).build();

        server.expect(requestTo("https://api.kvkbase.nl/api/v1/kvk/99999999"))
              .andRespond(withStatus(HttpStatus.NOT_FOUND));

        assertThatThrownBy(() -> service.ophalenOpKvkNummer("99999999"))
            .isInstanceOf(BedrijfNietGevondenException.class);
    }
}

Praktijkscenario: KVK-validatie bij orderverwerking

Een veelvoorkomend use-case: controleer bij het aanmaken van een B2B-order of de klant een actief KVK-nummer heeft.

@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 maakOrderAan(OrderRequest request) {
        // Verifieer KVK vóór order aanmaken
        if (request.kvkNummer() != null) {
            boolean actief = kvkbaseService.isActiefIngeschreven(request.kvkNummer());
            if (!actief) {
                throw new OngeldigBedrijfException(
                    "KVK-nummer " + request.kvkNummer() + " is niet actief"
                );
            }

            // Verrijk order met bedrijfsnaam
            KvkBedrijf bedrijf = kvkbaseService.ophalenOpKvkNummer(request.kvkNummer());
            return orderRepository.save(new Order(
                request.kvkNummer(),
                bedrijf.naam(),
                request.producten()
            ));
        }

        return orderRepository.save(new Order(null, null, request.producten()));
    }
}

Samenvatting

Met een paar Spring Boot-componenten heb je een robuuste KVK-integratie:

  • KvkbaseProperties voor configuratie via application.yml
  • RestClient bean met API-key en timeout
  • KvkbaseService met gecentraliseerde foutafhandeling
  • @Cacheable voor efficiënt hergebruik van resultaten
  • Custom exceptions voor duidelijk foutonderscheid
  • Unit tests via MockRestServiceServer

Lees ook: KVK API integreren met Node.js en TypeScript voor een vergelijkbare aanpak in JavaScript-omgevingen, en Bedrijfsgegevens opvragen voor je webshop voor checkout-integraties specifiek gericht op e-commerce.