Backend Integration - Java / Spring Boot
Spring Boot with WebClient (from spring-boot-starter-webflux) gives you reactive, non-blocking HTTP calls to PickPoint alongside a standard MVC REST layer.
1
Dependencies (Maven)
<!-- pom.xml -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId> <!-- for WebClient -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>2
Configuration and PickPoint client bean
Store the key in an environment variable and inject it via @Value:
# application.properties
pickpoint.key=${PICKPOINT_KEY} # loaded from env var// PickPointClient.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
@Component
public class PickPointClient {
private final WebClient client;
public PickPointClient(@Value("${pickpoint.key}") String key) {
this.client = WebClient.builder()
.baseUrl("https://api.pickpoint.io/v2")
.defaultHeader("X-Api-Key", key)
.build();
}
public WebClient get() { return client; }
}3
Geocoding controller
// GeocodingController.java
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/geo")
public class GeocodingController {
private final WebClient pp;
public GeocodingController(PickPointClient client) {
this.pp = client.get();
}
// GET /geo/forward?q=Eiffel+Tower&limit=5
@GetMapping("/forward")
public Mono<String> forward(@RequestParam String q,
@RequestParam(defaultValue = "5") int limit,
@RequestParam(required = false) String countrycodes,
@RequestParam(required = false) String viewbox) {
return pp.get()
.uri(b -> b.path("/geocode/forward")
.queryParam("q", q)
.queryParam("limit", limit)
.queryParamIfPresent("countrycodes", java.util.Optional.ofNullable(countrycodes))
.queryParamIfPresent("viewbox", java.util.Optional.ofNullable(viewbox))
.build())
.retrieve().bodyToMono(String.class);
}
// GET /geo/reverse?lat=48.85&lon=2.29&zoom=18
@GetMapping("/reverse")
public Mono<String> reverse(@RequestParam double lat,
@RequestParam double lon,
@RequestParam(defaultValue = "18") int zoom) {
return pp.get()
.uri(b -> b.path("/geocode/reverse")
.queryParam("lat", lat).queryParam("lon", lon).queryParam("zoom", zoom)
.build())
.retrieve().bodyToMono(String.class);
}
// GET /geo/search?q=Baker+St&viewbox=…
@GetMapping("/search")
public Mono<String> search(@RequestParam String q,
@RequestParam(required = false) String viewbox,
@RequestParam(defaultValue = "6") int limit) {
return pp.get()
.uri(b -> b.path("/address/search")
.queryParam("q", q).queryParam("limit", limit)
.queryParamIfPresent("viewbox", java.util.Optional.ofNullable(viewbox))
.build())
.retrieve().bodyToMono(String.class);
}
}4
Routing controller
// RoutingController.java
@RestController
public class RoutingController {
private final WebClient pp;
public RoutingController(PickPointClient client) { this.pp = client.get(); }
// POST /route
@PostMapping("/route")
public Mono<String> route(@RequestBody String body) {
return pp.post().uri("/route")
.header("Content-Type", "application/json")
.bodyValue(body)
.retrieve().bodyToMono(String.class);
}
// POST /route/optimized
@PostMapping("/route/optimized")
public Mono<String> routeOptimized(@RequestBody String body) {
return pp.post().uri("/route/optimized")
.header("Content-Type", "application/json")
.bodyValue(body)
.retrieve().bodyToMono(String.class);
}
}5
Device tracking - REST
// DeviceController.java
@RestController
@RequestMapping("/devices")
public class DeviceController {
private final WebClient pp;
public DeviceController(PickPointClient client) { this.pp = client.get(); }
@GetMapping
public Mono<String> list() {
return pp.get().uri("/devices").retrieve().bodyToMono(String.class);
}
@GetMapping("/{uid}/tracks")
public Mono<String> tracks(@PathVariable String uid,
@RequestParam(required = false) String from,
@RequestParam(required = false) String to) {
return pp.get()
.uri(b -> b.path("/devices/{uid}/tracks")
.queryParamIfPresent("from", java.util.Optional.ofNullable(from))
.queryParamIfPresent("to", java.util.Optional.ofNullable(to))
.build(uid))
.retrieve().bodyToMono(String.class);
}
}For the WebSocket subscriber relay, implement a WebSocketHandler that opens a Reactor Netty connection to wss://ws.pickpoint.io with the API key and pipes location:added events to the client session.