Geolocation API
- Getting the Current Position
- Options
- Watching Position Changes
- Handling Errors
- Checking Availability
- Calling From Background Threads
- Position Data
The Geolocation API gives server-side Java code access to the browser Geolocation API. It exposes two static entry points: Geolocation.getPosition() for a single reading and Geolocation.watchPosition() for a continuous stream.
|
Note
|
The browser shows its own permission dialog the first time the application asks for a location. The dialog is controlled by the browser — Vaadin can’t style it, suppress it, or detect when it’s shown. Geolocation also requires a secure context (HTTPS), except on localhost during development.
|
Getting the Current Position
Use Geolocation.getPosition() to request the device’s current position once — the typical pattern for a "Use my location" button. The call takes two consumers, one for the GeolocationPosition and one for the GeolocationError; whichever the browser produces is delivered on the UI thread:
Source code
Java
Button locate = new Button("Use my location", e -> Geolocation.getPosition(
pos -> {
double lat = pos.coords().latitude();
double lon = pos.coords().longitude();
map.setCenter(new Coordinate(lon, lat));
},
err -> Notification.show("Could not determine your location.")));The call returns immediately. Exactly one of the consumers is invoked once the browser reports a result. See Handling Errors for the full set of error reasons.
Options
GeolocationOptions controls three knobs on a request:
highAccuracy(boolean)-
Asks for the most accurate reading the device can produce — typically GPS on mobile devices. Uses more battery and can take longer. Default:
false. timeout(Duration)-
Maximum time the browser may spend before failing with a
TIMEOUTerror. Default: no timeout. maximumAge(Duration)-
How old a cached reading may be while still acceptable.
Duration.ZERO(the default) refuses cached readings. Larger values save battery.
Pass an instance as the last argument to Geolocation.getPosition(). A setter that’s never called leaves the corresponding field at the browser default.
Source code
Java
GeolocationOptions opts = GeolocationOptions.builder()
.highAccuracy(true)
.timeout(Duration.ofSeconds(10))
.maximumAge(Duration.ZERO)
.build();
Geolocation.getPosition(pos -> { /* ... */ }, err -> { /* ... */ }, opts);Use the builder rather than the canonical constructor — it accepts Duration for the time-based fields and gives every option a named setter. There are also int overloads on timeout() and maximumAge() if you already have a millisecond value on hand.
Watching Position Changes
Geolocation.watchPosition(Component) starts a continuous watch and returns a GeolocationWatcher tied to the owner component. The owner determines the watch’s lifecycle: the watch activates as soon as the owner is attached to a UI and is cancelled automatically when the component detaches. Pass the view itself in most cases — the watch then lives exactly as long as the view is open, and you don’t have to remember to clean up. The browser reports new positions as the device moves. There are two ways to consume them — pick the one that fits the destination.
The same GeolocationOptions described above can be passed as a second argument to control accuracy, timeout, and cache-age:
Source code
Java
GeolocationWatcher watcher = Geolocation.watchPosition(this,
GeolocationOptions.builder().highAccuracy(true).build());Listener Style
Use addPositionListener() when every reading needs to be processed in order — for example, persisting each point to a database, appending to a route log, or forwarding to an external service. The position consumer fires for every successful reading and the error consumer fires for every error.
Source code
Java
GeolocationWatcher watcher = Geolocation.watchPosition(this);
watcher.addPositionListener(
pos -> repository.save(pos),
err -> log.warn("location error: {}", err.debugInfo()));Both consumers are required. To opt out of one side, pass an empty lambda explicitly. Add multiple listeners if needed; the returned Registration removes both at once. Listeners stay attached across stop()/resume() cycles.
Signal Style
Use positionSignal() when the goal is to reflect the current position in the UI — the signal always holds the latest reading, so subscribers naturally show the most recent value and the listener-style "process every point" semantics don’t get in the way. The signal value is one of three sealed GeolocationResult subtypes, so a switch is exhaustive at compile time:
Source code
Java
GeolocationWatcher watcher = Geolocation.watchPosition(this);
status.bindText(watcher.positionSignal().map(result -> switch (result) {
case GeolocationPending p -> "Waiting for first reading…";
case GeolocationPosition pos -> "";
case GeolocationError err -> "Couldn't read location.";
}));
Signal.effect(this, () -> {
if (watcher.positionSignal().get() instanceof GeolocationPosition pos) {
map.setCenter(new Coordinate(pos.coords().longitude(),
pos.coords().latitude()));
}
});GeolocationPending-
Initial state, held until the browser reports the first reading.
GeolocationPosition-
A successful reading. Replaces the previous value on every update.
GeolocationError-
The browser reported an error.
Stopping and Resuming
Call stop() on the GeolocationWatcher to end tracking from application code — for instance, behind a "Stop tracking" button. The handle stays usable: watcher.resume() installs a fresh browser watch on the same watcher and any signal subscribers and listeners keep working. resume() requires the owner to be attached to a UI; calling it after the owner has detached throws IllegalStateException.
activeSignal() reports whether the watcher is currently receiving updates. You can use it, for instance, to drive separate Start and Stop buttons:
Source code
Java
Button start = new Button("Start tracking", e -> watcher.resume());
start.bindVisible(watcher.activeSignal().map(active -> !active));
Button stop = new Button("Stop tracking", e -> watcher.stop());
stop.bindVisible(watcher.activeSignal());positionSignal() is reset to GeolocationPending on each resume().
|
Note
|
Permission revoke caveat
If the user revokes location permission while a watch is active and then grants it again, the browser silently stops delivering updates to the existing watch. To recover, call stop() followed by resume(). To do this automatically, subscribe to Geolocation.availabilityHintSignal() and trigger the cycle when availability returns to GRANTED.
|
Handling Errors
Switch on errorCode() on the GeolocationError to get a typed GeolocationErrorCode enum — the switch is exhaustive at compile time and unknown future codes from the browser surface as UNKNOWN:
Source code
Java
Geolocation.getPosition(pos -> showOnMap(pos), err -> {
String userMessage = switch (err.errorCode()) {
case PERMISSION_DENIED ->
"Location is blocked. Please enable it in your browser settings.";
case POSITION_UNAVAILABLE ->
"Could not determine your location.";
case TIMEOUT ->
"Location request took too long. Please try again.";
case UNKNOWN ->
"Couldn't read location.";
};
Notification.show(userMessage);
});For diagnostics, log err.debugInfo() alongside the raw code() — it’s a free-form browser string and isn’t meant for end users.
Checking Availability
Geolocation.availabilityHintSignal() is a reactive signal of GeolocationAvailability reporting whether the feature is usable and what permission state the origin has. It’s called a hint mainly because of UNKNOWN: some browsers (notably Safari) never expose permission state, so the signal can’t tell you anything meaningful. The other values are reliable enough to act on, with one footnote covered below.
Reading the signal does not trigger a permission dialog — it tells you whether one would appear on the next getPosition() or watchPosition() call.
UNSUPPORTED-
The browser cannot do geolocation in this context (insecure origin or a blocking iframe policy) and no user action will change that. Hide the request UI entirely.
GRANTED-
Permission has already been granted; a subsequent call should proceed silently. Reasonable default: auto-fetch on attach.
DENIED-
Permission has already been denied; a subsequent call will fail with
PERMISSION_DENIEDwithout prompting. Reasonable default: skip the request button or show a "blocked" message. PROMPT-
No decision has been recorded yet; the next call will show the browser’s permission dialog. Render the request button.
UNKNOWN-
The browser doesn’t expose permission state — the signal has no useful guess. Render the default request button and let the user click it.
Always handle the error consumer of getPosition() and the watcher anyway. Even with a GRANTED hint, the call can still fail for unrelated reasons (timeout, no position fix), or the user can revoke permission mid-session faster than the signal catches up.
Typical use is to decide how to render before the user clicks anything — for example, auto-fetching on return visits when permission is already granted, or hiding the location button entirely when the feature is unsupported:
Source code
Java
@Override
protected void onAttach(AttachEvent event) {
if (Geolocation.availabilityHintSignal()
.peek() == GeolocationAvailability.GRANTED) {
Geolocation.getPosition(this::populateLocalContent,
err -> { /* ignore */ });
}
}Subscribe with Signal.effect() to react to changes — for instance, if the user revokes permission while the view is open.
Calling From Background Threads
The static methods above resolve the target UI through UI.getCurrent(), which only works on a request-handling thread. Background threads (executors, scheduled tasks, push handlers) need to pass the UI explicitly. Every entry point has an overload accepting a UI argument:
Source code
Java
@Override
protected void onAttach(AttachEvent event) {
UI ui = event.getUI();
ScheduledFuture<?> task = scheduler.scheduleAtFixedRate(
() -> Geolocation.getPosition(this::updateMap, this::showError,
ui),
0, 5, TimeUnit.MINUTES);
addDetachListener(e -> task.cancel(false));
}The callbacks themselves are still invoked on the UI thread, so they can update components directly without an ui.access() wrapper. Geolocation.availabilityHintSignal(ui) and the four-argument getPosition(onSuccess, onError, options, ui) overload follow the same pattern. watchPosition(Component) already resolves the UI through the owner component, so it works from any thread without a special overload.
Position Data
A GeolocationPosition contains GeolocationCoordinates and a timestamp() (milliseconds since the Unix epoch — use timestampAsInstant() for an Instant).
| Field | Type | Description |
|---|---|---|
|
| Decimal degrees, positive north. Always present. |
|
| Decimal degrees, positive east. Always present. |
|
| 1-sigma horizontal accuracy in meters — the true position lies within this distance with about 68% probability. Always present. |
|
| Meters above the WGS 84 ellipsoid, or |
|
| 1-sigma vertical accuracy in meters, or |
|
| Direction of travel in degrees clockwise from true north (0 = north, 90 = east), or |
|
| Ground speed in meters per second, or |
Boxed Double fields can be null. A laptop without GPS typically reports null for altitude, heading, and speed; a mobile phone with GPS typically reports all of them.