google-site-verification: google61fe8ba583a51912.html
Real-time Location Intelligence: Implementing Proximity Alerts in Java

Proximity alerts notify users or systems when a target enters or exits a defined geographical area. This capability is crucial for location-based services, security systems, logistics tracking, and real-time monitoring applications. Let's explore how to build robust proximity alert systems in Java.

Why Proximity Alerts in Java?

Key use cases:

  • Geofencing - Virtual boundaries for assets and people
  • Location-based Marketing - Notify customers near retail locations
  • Fleet Management - Monitor vehicle entry/exit from designated areas
  • Security Systems - Alert when assets move outside safe zones
  • Smart Cities - Traffic management and public safety notifications

Core Proximity Detection Engine

1. Geographical Calculations Service

@Service @Slf4j public class GeoCalculationService { private static final double EARTH_RADIUS_METERS = 6371000; // Earth's radius in meters /** * Calculate distance between two points using Haversine formula */ public double calculateDistance(Location point1, Location point2) { double lat1 = Math.toRadians(point1.getLatitude()); double lon1 = Math.toRadians(point1.getLongitude()); double lat2 = Math.toRadians(point2.getLatitude()); double lon2 = Math.toRadians(point2.getLongitude()); double dLat = lat2 - lat1; double dLon = lon2 - lon1; double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) * Math.sin(dLon / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return EARTH_RADIUS_METERS * c; } /** * Check if point is within circular geofence */ public boolean isPointInCircle(Location point, Location center, double radiusMeters) { double distance = calculateDistance(point, center); return distance <= radiusMeters; } /** * Check if point is within polygon using ray casting algorithm */ public boolean isPointInPolygon(Location point, List<Location> polygon) { if (polygon.size() < 3) { throw new IllegalArgumentException("Polygon must have at least 3 points"); } boolean inside = false; int n = polygon.size(); for (int i = 0, j = n - 1; i < n; j = i++) { Location vertexI = polygon.get(i); Location vertexJ = polygon.get(j); if (((vertexI.getLatitude() > point.getLatitude()) != (vertexJ.getLatitude() > point.getLatitude())) && (point.getLongitude() < (vertexJ.getLongitude() - vertexI.getLongitude()) * (point.getLatitude() - vertexI.getLatitude()) / (vertexJ.getLatitude() - vertexI.getLatitude()) + vertexI.getLongitude())) { inside = !inside; } } return inside; } /** * Check if point is within rectangle defined by northwest and southeast corners */ public boolean isPointInRectangle(Location point, Location northwest, Location southeast) { return point.getLatitude() <= northwest.getLatitude() && point.getLatitude() >= southeast.getLatitude() && point.getLongitude() >= northwest.getLongitude() && point.getLongitude() <= southeast.getLongitude(); } /** * Calculate bearing between two points */ public double calculateBearing(Location start, Location end) { double lat1 = Math.toRadians(start.getLatitude()); double lon1 = Math.toRadians(start.getLongitude()); double lat2 = Math.toRadians(end.getLatitude()); double lon2 = Math.toRadians(end.getLongitude()); double dLon = lon2 - lon1; double y = Math.sin(dLon) * Math.cos(lat2); double x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon); double bearing = Math.toDegrees(Math.atan2(y, x)); return (bearing + 360) % 360; // Normalize to 0-360 } /** * Calculate destination point given distance and bearing */ public Location calculateDestination(Location start, double bearing, double distanceMeters) { double lat1 = Math.toRadians(start.getLatitude()); double lon1 = Math.toRadians(start.getLongitude()); double bearingRad = Math.toRadians(bearing); double angularDistance = distanceMeters / EARTH_RADIUS_METERS; double lat2 = Math.asin(Math.sin(lat1) * Math.cos(angularDistance) + Math.cos(lat1) * Math.sin(angularDistance) * Math.cos(bearingRad)); double lon2 = lon1 + Math.atan2(Math.sin(bearingRad) * Math.sin(angularDistance) * Math.cos(lat1), Math.cos(angularDistance) - Math.sin(lat1) * Math.sin(lat2)); return new Location(Math.toDegrees(lat2), Math.toDegrees(lon2)); } }

2. Geofence Management Service

@Service @Slf4j public class GeofenceService { private final GeoCalculationService geoCalculator; private final GeofenceRepository geofenceRepository; private final Map<String, CachedGeofence> geofenceCache = new ConcurrentHashMap<>(); public GeofenceService(GeoCalculationService geoCalculator, GeofenceRepository geofenceRepository) { this.geoCalculator = geoCalculator; this.geofenceRepository = geofenceRepository; loadGeofencesIntoCache(); } public Geofence createGeofence(CreateGeofenceRequest request) { Geofence geofence = Geofence.builder() .id(UUID.randomUUID().toString()) .name(request.getName()) .description(request.getDescription()) .type(request.getType()) .coordinates(request.getCoordinates()) .radius(request.getRadius()) .isActive(true) .createdAt(Instant.now()) .updatedAt(Instant.now()) .metadata(request.getMetadata()) .build(); Geofence saved = geofenceRepository.save(geofence); cacheGeofence(saved); log.info("Created geofence: {}", saved.getName()); return saved; } public List<GeofenceCheckResult> checkLocationAgainstGeofences(Location location, String deviceId) { List<GeofenceCheckResult> results = new ArrayList<>(); for (CachedGeofence cachedGeofence : geofenceCache.values()) { if (!cachedGeofence.isActive()) { continue; } boolean isInside = checkPointInGeofence(location, cachedGeofence); GeofenceCheckResult result = GeofenceCheckResult.builder() .geofenceId(cachedGeofence.getId()) .geofenceName(cachedGeofence.getName()) .deviceId(deviceId) .location(location) .isInside(isInside) .checkedAt(Instant.now()) .build(); results.add(result); } return results; } public GeofenceTransition checkGeofenceTransition(Location currentLocation, Location previousLocation, String deviceId) { List<GeofenceCheckResult> currentResults = checkLocationAgainstGeofences(currentLocation, deviceId); List<GeofenceCheckResult> previousResults = checkLocationAgainstGeofences(previousLocation, deviceId); for (GeofenceCheckResult current : currentResults) { for (GeofenceCheckResult previous : previousResults) { if (current.getGeofenceId().equals(previous.getGeofenceId())) { boolean wasInside = previous.isInside(); boolean isNowInside = current.isInside(); if (wasInside && !isNowInside) { return GeofenceTransition.builder() .type(TransitionType.EXIT) .geofenceId(current.getGeofenceId()) .geofenceName(current.getGeofenceName()) .deviceId(deviceId) .location(currentLocation) .transitionTime(Instant.now()) .build(); } else if (!wasInside && isNowInside) { return GeofenceTransition.builder() .type(TransitionType.ENTER) .geofenceId(current.getGeofenceId()) .geofenceName(current.getGeofenceName()) .deviceId(deviceId) .location(currentLocation) .transitionTime(Instant.now()) .build(); } } } } return null; // No transition detected } public List<Geofence> findGeofencesNearLocation(Location location, double radiusMeters) { return geofenceCache.values().stream() .filter(CachedGeofence::isActive) .filter(geofence -> isGeofenceNearLocation(geofence, location, radiusMeters)) .map(this::mapToGeofence) .collect(Collectors.toList()); } private boolean checkPointInGeofence(Location location, CachedGeofence geofence) { switch (geofence.getType()) { case CIRCLE: Location center = geofence.getCoordinates().get(0); return geoCalculator.isPointInCircle(location, center, geofence.getRadius()); case POLYGON: return geoCalculator.isPointInPolygon(location, geofence.getCoordinates()); case RECTANGLE: if (geofence.getCoordinates().size() == 2) { return geoCalculator.isPointInRectangle(location, geofence.getCoordinates().get(0), geofence.getCoordinates().get(1)); } return false; default: log.warn("Unknown geofence type: {}", geofence.getType()); return false; } } private boolean isGeofenceNearLocation(CachedGeofence geofence, Location location, double radiusMeters) { // For circles, check distance to center if (geofence.getType() == GeofenceType.CIRCLE) { Location center = geofence.getCoordinates().get(0); double distance = geoCalculator.calculateDistance(location, center); return distance <= (radiusMeters + geofence.getRadius()); } // For polygons and rectangles, check if any vertex is within radius return geofence.getCoordinates().stream() .anyMatch(vertex -> geoCalculator.calculateDistance(location, vertex) <= radiusMeters); } private void loadGeofencesIntoCache() { List<Geofence> activeGeofences = geofenceRepository.findByIsActiveTrue(); activeGeofences.forEach(this::cacheGeofence); log.info("Loaded {} geofences into cache", activeGeofences.size()); } private void cacheGeofence(Geofence geofence) { geofenceCache.put(geofence.getId(), mapToCachedGeofence(geofence)); } private CachedGeofence mapToCachedGeofence(Geofence geofence) { return CachedGeofence.builder() .id(geofence.getId()) .name(geofence.getName()) .type(geofence.getType()) .coordinates(geofence.getCoordinates()) .radius(geofence.getRadius()) .isActive(geofence.isActive()) .metadata(geofence.getMetadata()) .build(); } private Geofence mapToGeofence(CachedGeofence cached) { return Geofence.builder() .id(cached.getId()) .name(cached.getName()) .type(cached.getType()) .coordinates(cached.getCoordinates()) .radius(cached.getRadius()) .isActive(cached.isActive()) .metadata(cached.getMetadata()) .build(); } }

Proximity Alert Engine

1. Real-time Alert Service

@Service @Slf4j public class ProximityAlertService { private final GeofenceService geofenceService; private final AlertNotificationService notificationService; private final AlertRepository alertRepository; private final DeviceTrackingService deviceTrackingService; // Track device states to detect transitions private final Map<String, DeviceGeofenceState> deviceStates = new ConcurrentHashMap<>(); public ProximityAlertService(GeofenceService geofenceService, AlertNotificationService notificationService, AlertRepository alertRepository, DeviceTrackingService deviceTrackingService) { this.geofenceService = geofenceService; this.notificationService = notificationService; this.alertRepository = alertRepository; this.deviceTrackingService = deviceTrackingService; } @Async public void processLocationUpdate(LocationUpdate update) { try { String deviceId = update.getDeviceId(); Location currentLocation = update.getLocation(); // Get previous location for transition detection Location previousLocation = deviceTrackingService.getLastKnownLocation(deviceId); if (previousLocation != null) { // Check for geofence transitions GeofenceTransition transition = geofenceService.checkGeofenceTransition( currentLocation, previousLocation, deviceId); if (transition != null) { handleGeofenceTransition(transition, update); } } // Check for proximity to points of interest checkProximityToPointsOfInterest(currentLocation, deviceId, update.getTimestamp()); // Update device tracking deviceTrackingService.updateDeviceLocation(deviceId, currentLocation, update.getTimestamp()); } catch (Exception e) { log.error("Failed to process location update for device: {}", update.getDeviceId(), e); } } public void checkProximityToTarget(Location deviceLocation, Location targetLocation, String deviceId, String targetId, double alertRadius) { double distance = geoCalculator.calculateDistance(deviceLocation, targetLocation); if (distance <= alertRadius) { ProximityAlert alert = ProximityAlert.builder() .id(UUID.randomUUID().toString()) .deviceId(deviceId) .targetId(targetId) .targetType("LOCATION") .distance(distance) .deviceLocation(deviceLocation) .targetLocation(targetLocation) .alertRadius(alertRadius) .triggeredAt(Instant.now()) .build(); triggerProximityAlert(alert); } } public void monitorDeviceProximity(String deviceId, String targetDeviceId, double alertRadius) { Location deviceLocation = deviceTrackingService.getLastKnownLocation(deviceId); Location targetLocation = deviceTrackingService.getLastKnownLocation(targetDeviceId); if (deviceLocation != null && targetLocation != null) { checkProximityToTarget(deviceLocation, targetLocation, deviceId, targetDeviceId, alertRadius); } } @Scheduled(fixedRate = 30000) // Check every 30 seconds public void performScheduledProximityChecks() { log.info("Performing scheduled proximity checks"); // Check all active devices against all geofences Map<String, Location> activeDevices = deviceTrackingService.getActiveDevices(); activeDevices.forEach((deviceId, location) -> { try { List<GeofenceCheckResult> results = geofenceService.checkLocationAgainstGeofences(location, deviceId); results.forEach(this::processGeofenceCheckResult); } catch (Exception e) { log.error("Failed to check proximity for device: {}", deviceId, e); } }); } private void handleGeofenceTransition(GeofenceTransition transition, LocationUpdate update) { log.info("Geofence {} detected: device {} {}", transition.getGeofenceName(), transition.getDeviceId(), transition.getType()); // Create alert GeofenceAlert alert = GeofenceAlert.builder() .id(UUID.randomUUID().toString()) .deviceId(transition.getDeviceId()) .geofenceId(transition.getGeofenceId()) .geofenceName(transition.getGeofenceName()) .transitionType(transition.getType()) .location(transition.getLocation()) .triggeredAt(transition.getTransitionTime()) .metadata(update.getMetadata()) .build(); // Save alert alertRepository.save(alert); // Send notifications notificationService.sendGeofenceAlert(alert); // Update device state updateDeviceGeofenceState(transition.getDeviceId(), transition.getGeofenceId(), transition.getType() == TransitionType.ENTER); } private void checkProximityToPointsOfInterest(Location location, String deviceId, Instant timestamp) { // Implementation would check against predefined points of interest // This is a simplified example List<PointOfInterest> nearbyPOIs = pointOfInterestService.findNearby(location, 1000); // 1km radius for (PointOfInterest poi : nearbyPOIs) { double distance = geoCalculator.calculateDistance(location, poi.getLocation()); if (distance <= poi.getNotificationRadius()) { triggerPointOfInterestAlert(deviceId, poi, distance, timestamp); } } } private void processGeofenceCheckResult(GeofenceCheckResult result) { // Update device state and detect state changes DeviceGeofenceState deviceState = deviceStates.computeIfAbsent( result.getDeviceId(), id -> new DeviceGeofenceState(id)); boolean stateChanged = deviceState.updateGeofenceState( result.getGeofenceId(), result.isInside(), result.getCheckedAt()); if (stateChanged) { // Handle state change (e.g., send notification for continuous monitoring) log.debug("Geofence state changed for device {} in geofence {}", result.getDeviceId(), result.getGeofenceName()); } } private void triggerProximityAlert(ProximityAlert alert) { alertRepository.save(alert); notificationService.sendProximityAlert(alert); log.info("Proximity alert triggered: device {} is {}m from target {}", alert.getDeviceId(), alert.getDistance(), alert.getTargetId()); } private void triggerPointOfInterestAlert(String deviceId, PointOfInterest poi, double distance, Instant timestamp) { POIProximityAlert alert = POIProximityAlert.builder() .id(UUID.randomUUID().toString()) .deviceId(deviceId) .poiId(poi.getId()) .poiName(poi.getName()) .poiType(poi.getType()) .distance(distance) .triggeredAt(timestamp) .build(); alertRepository.save(alert); notificationService.sendPOIProximityAlert(alert); } private void updateDeviceGeofenceState(String deviceId, String geofenceId, boolean isInside) { DeviceGeofenceState state = deviceStates.computeIfAbsent( deviceId, id -> new DeviceGeofenceState(id)); state.updateGeofenceState(geofenceId, isInside, Instant.now()); } }

Notification Service

1. Multi-channel Alert Notification

@Service @Slf4j public class AlertNotificationService { private final EmailService emailService; private final SMSService smsService; private final PushNotificationService pushService; private final WebSocketService webSocketService; private final AlertTemplateService templateService; public AlertNotificationService(EmailService emailService, SMSService smsService, PushNotificationService pushService, WebSocketService webSocketService, AlertTemplateService templateService) { this.emailService = emailService; this.smsService = smsService; this.pushService = pushService; this.webSocketService = webSocketService; this.templateService = templateService; } public void sendGeofenceAlert(GeofenceAlert alert) { // Get notification preferences for the device/user NotificationPreferences preferences = getNotificationPreferences(alert.getDeviceId()); // Prepare notification message String message = templateService.generateGeofenceAlertMessage(alert); String subject = templateService.generateGeofenceAlertSubject(alert); // Send via configured channels if (preferences.isEmailEnabled()) { sendEmailAlert(alert, subject, message, preferences.getEmailRecipients()); } if (preferences.isSmsEnabled()) { sendSMSAlert(alert, message, preferences.getPhoneNumbers()); } if (preferences.isPushEnabled()) { sendPushAlert(alert, message, preferences.getDeviceTokens()); } // Always send to WebSocket for real-time dashboard updates sendWebSocketAlert(alert, message); log.info("Sent geofence alert notifications for device: {}", alert.getDeviceId()); } public void sendProximityAlert(ProximityAlert alert) { NotificationPreferences preferences = getNotificationPreferences(alert.getDeviceId()); String message = templateService.generateProximityAlertMessage(alert); if (preferences.isPushEnabled()) { sendPushAlert(alert, message, preferences.getDeviceTokens()); } sendWebSocketAlert(alert, message); log.debug("Sent proximity alert for device: {}", alert.getDeviceId()); } public void sendPOIProximityAlert(POIProximityAlert alert) { // For POI alerts, we might want different notification rules String message = templateService.generatePOIAlertMessage(alert); sendPushAlert(alert, message, getDeviceTokens(alert.getDeviceId())); sendWebSocketAlert(alert, message); } public void sendBulkAlerts(List<Alert> alerts) { // Group alerts by device for efficient notification Map<String, List<Alert>> alertsByDevice = alerts.stream() .collect(Collectors.groupingBy(Alert::getDeviceId)); alertsByDevice.forEach((deviceId, deviceAlerts) -> { if (deviceAlerts.size() > 1) { // Send consolidated alert for multiple events sendConsolidatedAlert(deviceId, deviceAlerts); } else { // Send individual alert sendGeofenceAlert((GeofenceAlert) deviceAlerts.get(0)); } }); } private void sendEmailAlert(Alert alert, String subject, String message, List<String> recipients) { try { EmailRequest emailRequest = EmailRequest.builder() .to(recipients) .subject(subject) .body(message) .template("geofence-alert") .variables(Map.of("alert", alert)) .build(); emailService.sendEmail(emailRequest); } catch (Exception e) { log.error("Failed to send email alert for device: {}", alert.getDeviceId(), e); } } private void sendSMSAlert(Alert alert, String message, List<String> phoneNumbers) { for (String phoneNumber : phoneNumbers) { try { SMSRequest smsRequest = SMSRequest.builder() .to(phoneNumber) .message(message) .build(); smsService.sendSMS(smsRequest); } catch (Exception e) { log.error("Failed to send SMS alert to: {}", phoneNumber, e); } } } private void sendPushAlert(Alert alert, String message, List<String> deviceTokens) { for (String deviceToken : deviceTokens) { try { PushNotificationRequest pushRequest = PushNotificationRequest.builder() .deviceToken(deviceToken) .title("Proximity Alert") .body(message) .data(Map.of( "alertId", alert.getId(), "type", alert.getType(), "timestamp", alert.getTriggeredAt().toString() )) .build(); pushService.sendNotification(pushRequest); } catch (Exception e) { log.error("Failed to send push alert to device token: {}", deviceToken, e); } } } private void sendWebSocketAlert(Alert alert, String message) { try { WebSocketMessage wsMessage = WebSocketMessage.builder() .type("PROXIMITY_ALERT") .payload(Map.of( "alert", alert, "message", message, "timestamp", Instant.now().toString() )) .build(); webSocketService.sendToUser(alert.getDeviceId(), wsMessage); } catch (Exception e) { log.error("Failed to send WebSocket alert for device: {}", alert.getDeviceId(), e); } } private void sendConsolidatedAlert(String deviceId, List<Alert> alerts) { // Implementation for consolidated alerts (multiple events in one notification) String consolidatedMessage = templateService.generateConsolidatedAlertMessage(alerts); NotificationPreferences preferences = getNotificationPreferences(deviceId); if (preferences.isPushEnabled()) { // Send consolidated push notification } log.info("Sent consolidated alert for {} events to device: {}", alerts.size(), deviceId); } private NotificationPreferences getNotificationPreferences(String deviceId) { // In practice, this would fetch from database or user preferences service return NotificationPreferences.builder() .emailEnabled(true) .smsEnabled(false) .pushEnabled(true) .emailRecipients(List.of("[email protected]")) .phoneNumbers(List.of()) .deviceTokens(List.of("device-token-" + deviceId)) .build(); } private List<String> getDeviceTokens(String deviceId) { // Implementation would fetch from device registry return List.of("device-token-" + deviceId); } }

Data Models

1. Core Domain Models

@Data @Builder public class Location { private Double latitude; private Double longitude; private Double altitude; private Float accuracy; private Float speed; private Float bearing; private Instant timestamp; public Location() {} public Location(Double latitude, Double longitude) { this.latitude = latitude; this.longitude = longitude; this.timestamp = Instant.now(); } } @Data @Builder public class Geofence { private String id; private String name; private String description; private GeofenceType type; private List<Location> coordinates; private Double radius; // For circular geofences private boolean isActive; private Instant createdAt; private Instant updatedAt; private Map<String, Object> metadata; } @Data @Builder public class GeofenceCheckResult { private String geofenceId; private String geofenceName; private String deviceId; private Location location; private boolean isInside; private Instant checkedAt; } @Data @Builder public class GeofenceTransition { private TransitionType type; private String geofenceId; private String geofenceName; private String deviceId; private Location location; private Instant transitionTime; } @Data @Builder public abstract class Alert { private String id; private String deviceId; private AlertType type; private Location location; private Instant triggeredAt; private Map<String, Object> metadata; } @Data @Builder public class GeofenceAlert extends Alert { private String geofenceId; private String geofenceName; private TransitionType transitionType; } @Data @Builder public class ProximityAlert extends Alert { private String targetId; private String targetType; private Location targetLocation; private Double distance; private Double alertRadius; } @Data @Builder public class LocationUpdate { private String deviceId; private Location location; private Instant timestamp; private Map<String, Object> metadata; } // Enums public enum GeofenceType { CIRCLE, POLYGON, RECTANGLE } public enum TransitionType { ENTER, EXIT, DWELL, LEFT } public enum AlertType { GEOFENCE_ENTER, GEOFENCE_EXIT, PROXIMITY, POI_NEARBY, FENCE_VIOLATION } // Supporting classes @Data @Builder public class CachedGeofence { private String id; private String name; private GeofenceType type; private List<Location> coordinates; private Double radius; private boolean isActive; private Map<String, Object> metadata; } @Data public class DeviceGeofenceState { private String deviceId; private Map<String, Boolean> geofenceStates; // geofenceId -> isInside private Instant lastUpdate; public DeviceGeofenceState(String deviceId) { this.deviceId = deviceId; this.geofenceStates = new HashMap<>(); this.lastUpdate = Instant.now(); } public boolean updateGeofenceState(String geofenceId, boolean isInside, Instant timestamp) { Boolean previousState = geofenceStates.put(geofenceId, isInside); this.lastUpdate = timestamp; return previousState == null || previousState != isInside; } public boolean isInsideGeofence(String geofenceId) { return geofenceStates.getOrDefault(geofenceId, false); } } @Data @Builder public class NotificationPreferences { private boolean emailEnabled; private boolean smsEnabled; private boolean pushEnabled; private List<String> emailRecipients; private List<String> phoneNumbers; private List<String> deviceTokens; }

REST Controller

1. Proximity Alert API

@RestController @RequestMapping("/api/proximity") @Slf4j public class ProximityAlertController { private final ProximityAlertService alertService; private final GeofenceService geofenceService; private final AlertRepository alertRepository; public ProximityAlertController(ProximityAlertService alertService, GeofenceService geofenceService, AlertRepository alertRepository) { this.alertService = alertService; this.geofenceService = geofenceService; this.alertRepository = alertRepository; } @PostMapping("/location-update") public ResponseEntity<Void> processLocationUpdate(@RequestBody LocationUpdate update) { try { alertService.processLocationUpdate(update); return ResponseEntity.accepted().build(); } catch (Exception e) { log.error("Failed to process location update", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @PostMapping("/geofences") public ResponseEntity<Geofence> createGeofence(@RequestBody CreateGeofenceRequest request) { try { Geofence geofence = geofenceService.createGeofence(request); return ResponseEntity.status(HttpStatus.CREATED).body(geofence); } catch (Exception e) { log.error("Failed to create geofence", e); return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } } @PostMapping("/check-proximity") public ResponseEntity<ProximityCheckResponse> checkProximity(@RequestBody ProximityCheckRequest request) { try { List<GeofenceCheckResult> results = geofenceService.checkLocationAgainstGeofences( request.getLocation(), request.getDeviceId()); ProximityCheckResponse response = ProximityCheckResponse.builder() .deviceId(request.getDeviceId()) .location(request.getLocation()) .checkedAt(Instant.now()) .results(results) .build(); return ResponseEntity.ok(response); } catch (Exception e) { log.error("Failed to check proximity", e); return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } } @GetMapping("/alerts/{deviceId}") public ResponseEntity<List<Alert>> getDeviceAlerts( @PathVariable String deviceId, @RequestParam(defaultValue = "24") Integer hours) { try { Instant since = Instant.now().minus(hours, ChronoUnit.HOURS); List<Alert> alerts = alertRepository.findByDeviceIdAndTriggeredAtAfter(deviceId, since); return ResponseEntity.ok(alerts); } catch (Exception e) { log.error("Failed to get alerts for device: {}", deviceId, e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } @PostMapping("/monitor-device-proximity") public ResponseEntity<Void> monitorDeviceProximity(@RequestBody DeviceProximityRequest request) { try { alertService.monitorDeviceProximity( request.getDeviceId(), request.getTargetDeviceId(), request.getAlertRadius() ); return ResponseEntity.accepted().build(); } catch (Exception e) { log.error("Failed to setup device proximity monitoring", e); return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } } @GetMapping("/geofences/nearby") public ResponseEntity<List<Geofence>> findNearbyGeofences( @RequestParam Double lat, @RequestParam Double lng, @RequestParam(defaultValue = "1000") Double radiusMeters) { try { Location location = new Location(lat, lng); List<Geofence> geofences = geofenceService.findGeofencesNearLocation(location, radiusMeters); return ResponseEntity.ok(geofences); } catch (Exception e) { log.error("Failed to find nearby geofences", e); return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } } // Request DTOs @Data public static class CreateGeofenceRequest { private String name; private String description; private GeofenceType type; private List<Location> coordinates; private Double radius; private Map<String, Object> metadata; } @Data public static class ProximityCheckRequest { private String deviceId; private Location location; } @Data @Builder public static class ProximityCheckResponse { private String deviceId; private Location location; private Instant checkedAt; private List<GeofenceCheckResult> results; } @Data public static class DeviceProximityRequest { private String deviceId; private String targetDeviceId; private Double alertRadius; } }

Configuration

1. Application Properties

# application.yml proximity: alert: enabled: true check-interval: 30000 max-geofence-radius: 50000 notification: email-enabled: true sms-enabled: false push-enabled: true geofence: cache-enabled: true cache-refresh-interval: 300000 spring: task: execution: pool: core-size: 10 max-size: 50 queue-capacity: 1000

Best Practices

  1. Performance Optimization - Use spatial indexes for geofence queries
  2. Scalability - Implement distributed caching for device states
  3. Accuracy - Consider using more precise geographical calculations for critical applications
  4. Battery Optimization - Adjust location update frequency based on use case
  5. Error Handling - Implement robust error handling for location services
  6. Privacy - Ensure compliance with data protection regulations
  7. Monitoring - Track alert volumes and system performance

Conclusion

Implementing proximity alerts in Java enables powerful location-aware applications across various domains. By leveraging the patterns shown here, developers can:

  • Create Virtual Boundaries - Implement geofencing for security and monitoring
  • Enable Real-time Notifications - Alert users based on location changes
  • Optimize Operations - Monitor asset movements and fleet locations
  • Enhance User Experience - Provide context-aware services
  • Ensure Safety - Monitor restricted areas and safety zones

The combination of geographical calculations, real-time processing, and multi-channel notifications creates a comprehensive proximity alert system that can scale from simple location tracking to complex enterprise monitoring solutions.

Java Observability, Logging Intelligence & AI-Driven Monitoring (APM, Tracing, Logs & Anomaly Detection)

https://macronepal.com/blog/beyond-metrics-observing-serverless-and-traditional-java-applications-with-thundra-apm/
Explains using Thundra APM to observe both serverless and traditional Java applications by combining tracing, metrics, and logs into a unified observability platform for faster debugging and performance insights.

https://macronepal.com/blog/dynatrace-oneagent-in-java-2/
Explains Dynatrace OneAgent for Java, which automatically instruments JVM applications to capture metrics, traces, and logs, enabling full-stack monitoring and root-cause analysis with minimal configuration.

https://macronepal.com/blog/lightstep-java-sdk-distributed-tracing-and-observability-implementation/
Explains Lightstep Java SDK for distributed tracing, helping developers track requests across microservices and identify latency issues using OpenTelemetry-based observability.

https://macronepal.com/blog/honeycomb-io-beeline-for-java-complete-guide-2/
Explains Honeycomb Beeline for Java, which provides high-cardinality observability and deep query capabilities to understand complex system behavior and debug distributed systems efficiently.

https://macronepal.com/blog/lumigo-for-serverless-in-java-complete-distributed-tracing-guide-2/
Explains Lumigo for Java serverless applications, offering automatic distributed tracing, log correlation, and error tracking to simplify debugging in cloud-native environments. (Lumigo Docs)

https://macronepal.com/blog/from-noise-to-signals-implementing-log-anomaly-detection-in-java-applications/
Explains how to detect anomalies in Java logs using behavioral patterns and machine learning techniques to separate meaningful incidents from noisy log data and improve incident response.

https://macronepal.com/blog/ai-powered-log-analysis-in-java-from-reactive-debugging-to-proactive-insights/
Explains AI-driven log analysis for Java applications, shifting from manual debugging to predictive insights that identify issues early and improve system reliability using intelligent log processing.

https://macronepal.com/blog/titliel-java-logging-best-practices/
Explains best practices for Java logging, focusing on structured logs, proper log levels, performance optimization, and ensuring logs are useful for debugging and observability systems.

https://macronepal.com/blog/seeking-a-loguru-for-java-the-quest-for-elegant-and-simple-logging/
Explains the search for simpler, more elegant logging frameworks in Java, comparing modern logging approaches that aim to reduce complexity while improving readability and developer experience.


Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper