Kamero

IP Geolocation in Java & Spring Boot: Get Location from IP

Java remains one of the most widely used languages for backend services. Adding IP geolocation to your Java application enables location-based personalization, fraud detection, and compliance features. This guide covers plain Java, Spring Boot, and reactive approaches.

Java HttpClient (Java 11+)

import java.net.http.*;
import java.net.URI;
import com.fasterxml.jackson.databind.ObjectMapper;

var client = HttpClient.newHttpClient();
var request = HttpRequest.newBuilder()
    .uri(URI.create("https://geo.kamero.ai/api/geo"))
    .timeout(Duration.ofSeconds(5))
    .GET()
    .build();

var response = client.send(request,
    HttpResponse.BodyHandlers.ofString());

var mapper = new ObjectMapper();
var geo = mapper.readValue(
    response.body(), GeoResponse.class);

System.out.printf("%s, %s (%s)%n",
    geo.city(), geo.country(), geo.timezone());

Data Model

// Using Java record (Java 16+)
public record GeoResponse(
    String ip,
    String city,
    String country,
    String countryRegion,
    String continent,
    String latitude,
    String longitude,
    String timezone,
    String postalCode,
    String region
) {}

Spring Boot with RestTemplate

@Service
public class GeoLocationService {

    private final RestTemplate rest;

    public GeoLocationService(RestTemplateBuilder builder) {
        this.rest = builder
            .setConnectTimeout(Duration.ofSeconds(3))
            .setReadTimeout(Duration.ofSeconds(5))
            .build();
    }

    public Optional<GeoResponse> getLocation() {
        try {
            var geo = rest.getForObject(
                "https://geo.kamero.ai/api/geo",
                GeoResponse.class);
            return Optional.ofNullable(geo);
        } catch (RestClientException e) {
            log.warn("Geo lookup failed: {}", e.getMessage());
            return Optional.empty();
        }
    }
}

Reactive with WebClient

@Service
public class ReactiveGeoService {

    private final WebClient webClient;

    public ReactiveGeoService(WebClient.Builder builder) {
        this.webClient = builder
            .baseUrl("https://geo.kamero.ai")
            .build();
    }

    public Mono<GeoResponse> getLocation() {
        return webClient.get()
            .uri("/api/geo")
            .retrieve()
            .bodyToMono(GeoResponse.class)
            .timeout(Duration.ofSeconds(5))
            .onErrorResume(e -> {
                log.warn("Geo lookup failed: {}",
                    e.getMessage());
                return Mono.empty();
            });
    }
}

Add Caching with Spring Cache

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
        var cache = new ConcurrentMapCacheManager("geo");
        return cache;
    }
}

@Service
public class CachedGeoService {

    private final GeoLocationService geoService;

    @Cacheable(value = "geo", key = "'current'")
    public Optional<GeoResponse> getLocation() {
        return geoService.getLocation();
    }

    @CacheEvict(value = "geo", allEntries = true)
    @Scheduled(fixedRate = 600_000) // 10 minutes
    public void evictCache() {
        log.debug("Geo cache evicted");
    }
}

Servlet Filter for Request Enrichment

@Component
@Order(1)
public class GeoLocationFilter extends OncePerRequestFilter {

    private final GeoLocationService geoService;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain) throws Exception {

        geoService.getLocation().ifPresent(geo -> {
            request.setAttribute("geoCountry",
                geo.country());
            request.setAttribute("geoCity",
                geo.city());
            request.setAttribute("geoTimezone",
                geo.timezone());
        });

        chain.doFilter(request, response);
    }
}

Use in a Controller

@RestController
@RequestMapping("/api")
public class LocationController {

    private final GeoLocationService geoService;

    @GetMapping("/my-location")
    public ResponseEntity<GeoResponse> getLocation() {
        return geoService.getLocation()
            .map(ResponseEntity::ok)
            .orElse(ResponseEntity
                .status(HttpStatus.SERVICE_UNAVAILABLE)
                .build());
    }

    @GetMapping("/greeting")
    public Map<String, String> greeting(
            HttpServletRequest request) {
        var country = (String) request
            .getAttribute("geoCountry");
        var greeting = switch (country) {
            case "JP" -> "こんにちは";
            case "ES", "MX" -> "¡Hola!";
            case "FR" -> "Bonjour";
            case "DE" -> "Hallo";
            default -> "Hello";
        };
        return Map.of("greeting", greeting);
    }
}

Key Takeaways

Try the API

Free IP geolocation with no API key — works with any Java HTTP client.

View Documentation →