This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 1007d56a7fde CAMEL-26170: camel-geocoder - Nominatim sets ZERO_RESULTS
when it finds no place (#29000)
1007d56a7fde is described below
commit 1007d56a7fde342a317ed5ae4683f1297b004a4b
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Nov 3 11:38:44 2026 +0100
CAMEL-26170: camel-geocoder - Nominatim sets ZERO_RESULTS when it finds no
place (#29000)
…ds no place
Nominatim answers [] for an address it does not find. The producer set the
status OK, cut off the brackets and parsed the empty text, which failed the
exchange with an IllegalArgumentException. An empty array is now
ZERO_RESULTS. A latitude and longitude with no place ({"error":"Unable to
geocode"}) is ZERO_RESULTS as well, and another error answer is ERROR;
both were OK before.
The Nominatim tests use a local HTTP server with recorded answers instead of
nominatim.openstreetmap.org.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---
.../apache/camel/catalog/components/geocoder.json | 2 +-
.../camel/catalog/docs/geocoder-component.adoc | 2 +
.../apache/camel/component/geocoder/geocoder.json | 2 +-
.../src/main/docs/geocoder-component.adoc | 2 +
.../camel/component/geocoder/GeoCoderEndpoint.java | 4 +-
.../geocoder/GeoCoderNominatimProducer.java | 25 +++++--
.../geocoder/GeoCoderNominatimAddressTest.java | 11 +--
.../component/geocoder/GeoCoderNominatimTest.java | 10 +--
.../geocoder/GeoCoderNominatimTestSupport.java | 84 ++++++++++++++++++++++
.../geocoder/GeoCoderNominatimZeroResultsTest.java | 81 +++++++++++++++++++++
.../test/resources/nominatim/reverse-sevilla.json | 1 +
.../nominatim/search-calle-marie-curie.json | 1 +
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 7 ++
13 files changed, 214 insertions(+), 18 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/geocoder.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/geocoder.json
index d8fd1660170a..459f1b883f46 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/geocoder.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/geocoder.json
@@ -43,7 +43,7 @@
},
"bodyTypes": {
"producerIn": { "carriers": [ "any" ], "description": "The body is not
read: the address or the latitude and longitude come from the
CamelGeoCoderAddress or CamelGeoCoderLatlng header, or else from the endpoint"
},
- "producerOut": { "carriers": [ "list", "unchanged" ], "byOption": [ {
"option": "type", "value": "NOMINATIM", "carriers": [ "text" ], "javaType":
"java.lang.String" }, { "option": "headersOnly", "value": "true", "carriers": [
"unchanged" ] } ], "description": "With Google, the array of
com.google.maps.model.GeocodingResult, empty for no results; unchanged when
Google denies the request, with the status in the CamelGeoCoderStatus header.
With Nominatim, its jsonv2 response as text: an [...]
+ "producerOut": { "carriers": [ "list", "unchanged" ], "byOption": [ {
"option": "type", "value": "NOMINATIM", "carriers": [ "text" ], "javaType":
"java.lang.String" }, { "option": "headersOnly", "value": "true", "carriers": [
"unchanged" ] } ], "description": "With Google, the array of
com.google.maps.model.GeocodingResult, empty for no results; unchanged when
Google denies the request, with the status in the CamelGeoCoderStatus header.
With Nominatim, its jsonv2 response as text: an [...]
},
"properties": {
"address": { "index": 0, "kind": "path", "displayName": "Address",
"group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "The geo address which should be prefixed with
address:" },
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/geocoder-component.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/geocoder-component.adoc
index 8da79a403ab9..d59b7e832c36 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/geocoder-component.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/geocoder-component.adoc
@@ -54,6 +54,8 @@ mode in use (`address` vs. `latlng`).
Camel will deliver the body as a String with the JSON response of Nominatim
(the `jsonv2` format):
an array with one place for an address, and an object for a latitude and
longitude.
+When Nominatim finds no place (an empty array for an address, or
`{"error":"Unable to geocode"}` for a latitude and longitude),
+the `CamelGeoCoderStatus` header is `ZERO_RESULTS`, and when it answers with
an error, the header is `ERROR`.
=== Body using a Google Server
diff --git
a/components/camel-geocoder/src/generated/resources/META-INF/org/apache/camel/component/geocoder/geocoder.json
b/components/camel-geocoder/src/generated/resources/META-INF/org/apache/camel/component/geocoder/geocoder.json
index d8fd1660170a..459f1b883f46 100644
---
a/components/camel-geocoder/src/generated/resources/META-INF/org/apache/camel/component/geocoder/geocoder.json
+++
b/components/camel-geocoder/src/generated/resources/META-INF/org/apache/camel/component/geocoder/geocoder.json
@@ -43,7 +43,7 @@
},
"bodyTypes": {
"producerIn": { "carriers": [ "any" ], "description": "The body is not
read: the address or the latitude and longitude come from the
CamelGeoCoderAddress or CamelGeoCoderLatlng header, or else from the endpoint"
},
- "producerOut": { "carriers": [ "list", "unchanged" ], "byOption": [ {
"option": "type", "value": "NOMINATIM", "carriers": [ "text" ], "javaType":
"java.lang.String" }, { "option": "headersOnly", "value": "true", "carriers": [
"unchanged" ] } ], "description": "With Google, the array of
com.google.maps.model.GeocodingResult, empty for no results; unchanged when
Google denies the request, with the status in the CamelGeoCoderStatus header.
With Nominatim, its jsonv2 response as text: an [...]
+ "producerOut": { "carriers": [ "list", "unchanged" ], "byOption": [ {
"option": "type", "value": "NOMINATIM", "carriers": [ "text" ], "javaType":
"java.lang.String" }, { "option": "headersOnly", "value": "true", "carriers": [
"unchanged" ] } ], "description": "With Google, the array of
com.google.maps.model.GeocodingResult, empty for no results; unchanged when
Google denies the request, with the status in the CamelGeoCoderStatus header.
With Nominatim, its jsonv2 response as text: an [...]
},
"properties": {
"address": { "index": 0, "kind": "path", "displayName": "Address",
"group": "producer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "The geo address which should be prefixed with
address:" },
diff --git a/components/camel-geocoder/src/main/docs/geocoder-component.adoc
b/components/camel-geocoder/src/main/docs/geocoder-component.adoc
index 8da79a403ab9..d59b7e832c36 100644
--- a/components/camel-geocoder/src/main/docs/geocoder-component.adoc
+++ b/components/camel-geocoder/src/main/docs/geocoder-component.adoc
@@ -54,6 +54,8 @@ mode in use (`address` vs. `latlng`).
Camel will deliver the body as a String with the JSON response of Nominatim
(the `jsonv2` format):
an array with one place for an address, and an object for a latitude and
longitude.
+When Nominatim finds no place (an empty array for an address, or
`{"error":"Unable to geocode"}` for a latitude and longitude),
+the `CamelGeoCoderStatus` header is `ZERO_RESULTS`, and when it answers with
an error, the header is `ERROR`.
=== Body using a Google Server
diff --git
a/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderEndpoint.java
b/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderEndpoint.java
index a1dacf752942..c4f13de0717e 100644
---
a/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderEndpoint.java
+++
b/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderEndpoint.java
@@ -51,8 +51,8 @@ import org.apache.camel.support.DefaultEndpoint;
description = "With Google, the array of
com.google.maps.model.GeocodingResult, empty for"
+ " no results; unchanged
when Google denies the request, with the status in"
+ " the CamelGeoCoderStatus
header. With Nominatim, its jsonv2 response as text:"
- + " an array of one place for
an address, an object for a latitude and"
- + " longitude. The first
result is also in the headers"))
+ + " an array of one place for
an address (empty for no results), an object"
+ + " for a latitude and
longitude. The first result is also in the headers"))
public class GeoCoderEndpoint extends DefaultEndpoint implements
EndpointServiceLocation {
@UriPath
diff --git
a/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderNominatimProducer.java
b/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderNominatimProducer.java
index b30ee8f23c08..af3932601e5d 100644
---
a/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderNominatimProducer.java
+++
b/components/camel-geocoder/src/main/java/org/apache/camel/component/geocoder/GeoCoderNominatimProducer.java
@@ -129,20 +129,33 @@ public class GeoCoderNominatimProducer extends
DefaultProducer {
exchange.getIn().setBody(place);
}
+ if (place != null) {
+ place = place.trim();
+ // search answers an array, of one place as we ask for limit=1,
and [] when nothing is found
+ if (place.startsWith("[") && place.endsWith("]")) {
+ place = place.substring(1, place.length() - 1).trim();
+ }
+ }
if (place == null || place.isEmpty()) {
exchange.getIn().setHeader(GeoCoderConstants.STATUS,
GeocoderStatus.ZERO_RESULTS);
return;
}
- exchange.getIn().setHeader(GeoCoderConstants.STATUS,
GeocoderStatus.OK);
- if (place.startsWith("[") && place.endsWith("]")) {
- place = place.substring(1, place.length() - 1);
- }
-
- // additional details
final Configuration conf =
Configuration.defaultConfiguration().addOptions(Option.SUPPRESS_EXCEPTIONS);
final DocumentContext doc = JsonPath.using(conf).parse(place);
+ Object error = doc.read("$['error']");
+ if (error instanceof String) {
+ // reverse answers {"error":"Unable to geocode"} when there is no
place at the latitude and longitude
+ exchange.getIn().setHeader(GeoCoderConstants.STATUS,
GeocoderStatus.ZERO_RESULTS);
+ return;
+ } else if (error != null) {
+ // an error answer such as {"error":{"code":400,"message":"..."}}
+ exchange.getIn().setHeader(GeoCoderConstants.STATUS,
GeocoderStatus.ERROR);
+ return;
+ }
+ exchange.getIn().setHeader(GeoCoderConstants.STATUS,
GeocoderStatus.OK);
+
exchange.getIn().setHeader(GeoCoderConstants.ADDRESS,
doc.read("$['display_name']"));
// just grab the first element and its lat and lon
diff --git
a/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimAddressTest.java
b/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimAddressTest.java
index 6294366c113a..4998bf71f8e6 100644
---
a/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimAddressTest.java
+++
b/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimAddressTest.java
@@ -18,19 +18,21 @@ package org.apache.camel.component.geocoder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
-import org.apache.camel.test.junit6.CamelTestSupport;
import org.junit.jupiter.api.Test;
-public class GeoCoderNominatimAddressTest extends CamelTestSupport {
+public class GeoCoderNominatimAddressTest extends GeoCoderNominatimTestSupport
{
@Test
public void testGeoCoder() throws Exception {
+ answerFromResource("search", "search-calle-marie-curie.json");
+
MockEndpoint mock = getMockEndpoint("mock:result");
mock.expectedMessageCount(1);
+ mock.expectedHeaderReceived(GeoCoderConstants.STATUS,
GeocoderStatus.OK);
mock.expectedHeaderReceived(GeoCoderConstants.COUNTRY_SHORT, "ES");
mock.expectedHeaderReceived(GeoCoderConstants.REGION_NAME,
"Andalucía");
+ mock.expectedHeaderReceived(GeoCoderConstants.POSTAL_CODE, "41092");
- // the address header overrides the endpoint configuration
template.sendBody("direct:start", "Test");
MockEndpoint.assertIsSatisfied(context);
@@ -41,7 +43,8 @@ public class GeoCoderNominatimAddressTest extends
CamelTestSupport {
return new RouteBuilder() {
public void configure() {
from("direct:start").to(
- "geocoder:address:calle marie curie, sevilla,
sevilla?type=NOMINATIM&serverUrl=RAW(https://nominatim.openstreetmap.org)")
+ "geocoder:address:calle marie curie, sevilla,
sevilla?type=NOMINATIM&serverUrl=RAW(" + serverUrl()
+ + ")")
.to("log:result")
.log("Location ${header.CamelGeocoderAddress} is at
lat/lng: ${header.CamelGeocoderLatlng}"
+ " and in city ${header.CamelGeoCoderCity} in
country ${header.CamelGeoCoderCountryLong}")
diff --git
a/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimTest.java
b/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimTest.java
index 3a1790fa175d..dfc749809df8 100644
---
a/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimTest.java
+++
b/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimTest.java
@@ -18,19 +18,21 @@ package org.apache.camel.component.geocoder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
-import org.apache.camel.test.junit6.CamelTestSupport;
import org.junit.jupiter.api.Test;
-public class GeoCoderNominatimTest extends CamelTestSupport {
+public class GeoCoderNominatimTest extends GeoCoderNominatimTestSupport {
@Test
public void testGeoCoder() throws Exception {
+ answerFromResource("reverse", "reverse-sevilla.json");
+
MockEndpoint mock = getMockEndpoint("mock:result");
mock.expectedMessageCount(1);
+ mock.expectedHeaderReceived(GeoCoderConstants.STATUS,
GeocoderStatus.OK);
mock.expectedHeaderReceived(GeoCoderConstants.COUNTRY_SHORT, "ES");
mock.expectedHeaderReceived(GeoCoderConstants.CITY, "Sevilla");
+ mock.expectedHeaderReceived(GeoCoderConstants.LATLNG, "37.38618880,
-5.99255350");
- // the address header overrides the endpoint configuration
template.sendBody("direct:start", "Test");
MockEndpoint.assertIsSatisfied(context);
@@ -41,7 +43,7 @@ public class GeoCoderNominatimTest extends CamelTestSupport {
return new RouteBuilder() {
public void configure() {
from("direct:start").to(
-
"geocoder:latlng:37.38619,-5.99255?type=NOMINATIM&serverUrl=RAW(https://nominatim.openstreetmap.org)")
+
"geocoder:latlng:37.38619,-5.99255?type=NOMINATIM&serverUrl=RAW(" + serverUrl()
+ ")")
.to("log:result")
.log("Location ${header.CamelGeocoderAddress} is at
lat/lng: ${header.CamelGeocoderLatlng}"
+ " and in city ${header.CamelGeoCoderCity} in
country ${header.CamelGeoCoderCountryLong}")
diff --git
a/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimTestSupport.java
b/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimTestSupport.java
new file mode 100644
index 000000000000..ed1d5c8c9235
--- /dev/null
+++
b/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimTestSupport.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.geocoder;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.sun.net.httpserver.HttpServer;
+import org.apache.camel.test.junit6.CamelTestSupport;
+
+/**
+ * Runs the Nominatim tests against a local HTTP server that answers the
recorded responses of
+ * nominatim.openstreetmap.org, so the build does not depend on the public
service.
+ */
+public abstract class GeoCoderNominatimTestSupport extends CamelTestSupport {
+
+ private final Map<String, String> answers = new ConcurrentHashMap<>();
+ private HttpServer server;
+
+ @Override
+ protected void setupResources() throws Exception {
+ server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
+ server.createContext("/", exchange -> {
+ String answer = answers.get(exchange.getRequestURI().getPath());
+ byte[] bytes = answer != null ?
answer.getBytes(StandardCharsets.UTF_8) : new byte[0];
+ exchange.getResponseHeaders().add("Content-Type",
"application/json; charset=utf-8");
+ exchange.sendResponseHeaders(answer != null ? 200 : 404,
bytes.length > 0 ? bytes.length : -1);
+ try (OutputStream body = exchange.getResponseBody()) {
+ body.write(bytes);
+ }
+ });
+ server.start();
+ }
+
+ @Override
+ protected void cleanupResources() throws Exception {
+ if (server != null) {
+ server.stop(0);
+ }
+ }
+
+ /**
+ * The URL of the local Nominatim server, for the serverUrl option.
+ */
+ protected String serverUrl() {
+ return "http://127.0.0.1:" + server.getAddress().getPort();
+ }
+
+ /**
+ * Sets the answer of the given operation (search or reverse).
+ */
+ protected void answer(String operation, String json) {
+ answers.put("/" + operation, json);
+ }
+
+ /**
+ * Sets the answer of the given operation (search or reverse) from a
recorded response in
+ * src/test/resources/nominatim.
+ */
+ protected void answerFromResource(String operation, String name) throws
IOException {
+ try (InputStream is = getClass().getResourceAsStream("/nominatim/" +
name)) {
+ answer(operation, new String(is.readAllBytes(),
StandardCharsets.UTF_8));
+ }
+ }
+}
diff --git
a/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimZeroResultsTest.java
b/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimZeroResultsTest.java
new file mode 100644
index 000000000000..97acc619fc30
--- /dev/null
+++
b/components/camel-geocoder/src/test/java/org/apache/camel/component/geocoder/GeoCoderNominatimZeroResultsTest.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.geocoder;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+public class GeoCoderNominatimZeroResultsTest extends
GeoCoderNominatimTestSupport {
+
+ @Test
+ public void testAddressNotFound() throws Exception {
+ // search answers an empty array when it finds nothing
+ answer("search", "[]");
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+ mock.expectedBodiesReceived("[]");
+ mock.expectedHeaderReceived(GeoCoderConstants.STATUS,
GeocoderStatus.ZERO_RESULTS);
+
+ template.sendBody("direct:address", "Test");
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testLatLngNotFound() throws Exception {
+ // reverse answers an error text when there is no place at the
latitude and longitude, such as at sea
+ answer("reverse", "{\"error\":\"Unable to geocode\"}");
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+ mock.expectedHeaderReceived(GeoCoderConstants.STATUS,
GeocoderStatus.ZERO_RESULTS);
+
+ template.sendBody("direct:latlng", "Test");
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Test
+ public void testErrorAnswer() throws Exception {
+ answer("reverse", "{\"error\":{\"code\":400,\"message\":\"Parameter
'lat' must be a number.\"}}");
+
+ MockEndpoint mock = getMockEndpoint("mock:result");
+ mock.expectedMessageCount(1);
+ mock.expectedHeaderReceived(GeoCoderConstants.STATUS,
GeocoderStatus.ERROR);
+
+ template.sendBody("direct:latlng", "Test");
+
+ MockEndpoint.assertIsSatisfied(context);
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ public void configure() {
+ from("direct:address")
+
.to("geocoder:address:nowhere?type=NOMINATIM&serverUrl=RAW(" + serverUrl() +
")")
+ .to("mock:result");
+
+ from("direct:latlng")
+
.to("geocoder:latlng:0,-30?type=NOMINATIM&serverUrl=RAW(" + serverUrl() + ")")
+ .to("mock:result");
+ }
+ };
+ }
+}
diff --git
a/components/camel-geocoder/src/test/resources/nominatim/reverse-sevilla.json
b/components/camel-geocoder/src/test/resources/nominatim/reverse-sevilla.json
new file mode 100644
index 000000000000..11a72c3d1c6a
--- /dev/null
+++
b/components/camel-geocoder/src/test/resources/nominatim/reverse-sevilla.json
@@ -0,0 +1 @@
+{"place_id":295378929,"licence":"Data © OpenStreetMap contributors, ODbL 1.0.
http://osm.org/copyright","osm_type":"node","osm_id":3814289959,"lat":"37.3861888","lon":"-5.9925535","category":"historic","type":"memorial","place_rank":30,"importance":0.23094323434513592,"addresstype":"historic","name":"Giraldillo","display_name":"Giraldillo,
Puerta de Palos, Santa Cruz, Casco Antiguo, Sevilla, Andalucía, 41004,
España","address":{"historic":"Giraldillo","road":"Puerta de Palos","neighbourh
[...]
\ No newline at end of file
diff --git
a/components/camel-geocoder/src/test/resources/nominatim/search-calle-marie-curie.json
b/components/camel-geocoder/src/test/resources/nominatim/search-calle-marie-curie.json
new file mode 100644
index 000000000000..b0d877fe7939
--- /dev/null
+++
b/components/camel-geocoder/src/test/resources/nominatim/search-calle-marie-curie.json
@@ -0,0 +1 @@
+[{"place_id":295982222,"licence":"Data © OpenStreetMap contributors, ODbL 1.0.
http://osm.org/copyright","osm_type":"way","osm_id":70971075,"lat":"37.4062844","lon":"-6.0059975","category":"highway","type":"primary","place_rank":26,"importance":0.05341013427939265,"addresstype":"primary","name":"Calle
Marie Curie","display_name":"Calle Marie Curie, Triana, Sevilla, Andalucía,
41092, España","address":{"road":"Calle Marie
Curie","suburb":"Triana","city":"Sevilla","province":"Sevilla","ISO [...]
\ No newline at end of file
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 2934640c888c..4758c945a4bb 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -853,6 +853,13 @@ onto the configuration of the endpoint. Before, they were,
so every later query
`location` already did the geolocation when its producer was created, so its
`CamelWeatherLocation` header was never
used. The `lat` and `lon` options of an endpoint are still used instead of the
location.
+=== camel-geocoder - Nominatim sets ZERO_RESULTS when it finds no place
+
+With `type=NOMINATIM`, an address that Nominatim does not find (it answers
`[]`) now sets the `CamelGeoCoderStatus`
+header to `ZERO_RESULTS`. Before, the exchange failed with an
`IllegalArgumentException`. A latitude and longitude with
+no place (Nominatim answers `{"error":"Unable to geocode"}`) now sets
`ZERO_RESULTS` too, and another error answer of
+Nominatim sets `ERROR`. Before, both set `OK` with no other headers.
+
=== camel-gson, camel-jsonb - the unmarshal type header needs
allowUnmarshallType
The Gson and JSON-B data formats now use the `CamelGsonUnmarshalType` and
`CamelJsonbUnmarshallType` headers to choose