Skip to content

Commit 6009213

Browse files
feat(deps): upgrade solr-solrj from 9.9.0 to 10.0.0 (#58)
Upgrades solrj to 10, but with an eye towards backwards compatilbity by leveraging the json wire format we already merged. Note: SolrInfoMBeanHandler (and thus the /admin/mbeans endpoint) was removed in Solr 10. When getCacheMetrics() or getHandlerMetrics() call this endpoint on a Solr 10 server, SolrJ throws RemoteSolrException (a RuntimeException) because the server returns an HTML 404 page instead of JSON. Add Solr 9.10 and 10 to the CI compatibility matrix, running integration tests against all supported versions (8.11, 9.4, 9.9, 9.10, 10) on every PR and push to main. Also update AGENTS.md to document Solr 10 compatibility status: the /admin/mbeans endpoint removal is handled gracefully, and all other functionality is verified working with solr:10-slim. --------- Signed-off-by: adityamparikh <aditya.m.parikh@gmail.com> Signed-off-by: Aditya Parikh <adityamparikh@apache.org> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent dbecfb3 commit 6009213

12 files changed

Lines changed: 73 additions & 45 deletions

File tree

.github/workflows/build-and-publish.yml

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,40 @@ jobs:
175175
retention-days: 7
176176

177177
# ============================================================================
178-
# Job 2: Publish Docker Images
178+
# Job 2: Solr Version Compatibility Tests
179+
# ============================================================================
180+
# Tests the server against multiple Solr versions using Testcontainers.
181+
# Runs in parallel so each Solr version is an independent job.
182+
# Tested versions: 8.11, 9.4, 9.9, 9.10, 10
183+
# ============================================================================
184+
solr-compatibility:
185+
name: Solr ${{ matrix.solr-version }} Compatibility
186+
runs-on: ubuntu-latest
187+
188+
strategy:
189+
fail-fast: false
190+
matrix:
191+
solr-version:
192+
- "8.11-slim"
193+
- "9.4-slim"
194+
- "9.9-slim"
195+
- "9.10-slim"
196+
- "10-slim"
197+
198+
steps:
199+
- name: Checkout code
200+
uses: actions/checkout@v4
201+
202+
- name: Set up Java
203+
uses: ./.github/actions/setup-java
204+
205+
- name: Run tests against Solr ${{ matrix.solr-version }}
206+
env:
207+
SOLR_VERSION: ${{ matrix.solr-version }}
208+
run: ./gradlew test "-Dsolr.test.image=solr:${SOLR_VERSION}"
209+
210+
# ============================================================================
211+
# Job 3: Publish Docker Images
179212
# ============================================================================
180213
# This job builds multi-platform Docker images using Jib and publishes them
181214
# to GitHub Container Registry (GHCR) and Docker Hub.

AGENTS.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,24 @@ The Solr Docker image used in tests is configurable via the `solr.test.image` sy
8181
./gradlew test -Dsolr.test.image=solr:8.11-slim # Solr 8.11
8282
./gradlew test -Dsolr.test.image=solr:9.4-slim # Solr 9.4
8383
./gradlew test -Dsolr.test.image=solr:9.9-slim # Solr 9.9 (default)
84+
./gradlew test -Dsolr.test.image=solr:9.10-slim # Solr 9.10
85+
./gradlew test -Dsolr.test.image=solr:10-slim # Solr 10
8486
```
8587

86-
**Tested compatible versions:** 8.11, 9.4, 9.9
88+
**Tested compatible versions:** 8.11, 9.4, 9.9, 9.10, 10
8789

88-
### Solr 10 Compatibility Notes
90+
### Solr 10 Compatibility
8991

90-
Solr 10 introduces breaking changes that will require updates to this project:
92+
Solr 10.0.0 is fully supported with the JSON wire format. The `/admin/mbeans` endpoint was
93+
removed in Solr 10; `getCacheMetrics()` and `getHandlerMetrics()` now catch `RuntimeException`
94+
(which covers `RemoteSolrException`) so they degrade gracefully and return `null`. Tests that
95+
check `cacheStats` and `handlerStats` already handle `null` values.
9196

92-
- **MBeans removal:** `SolrInfoMBeanHandler` is removed. `CollectionService.getCollectionStats()` uses `/admin/mbeans` for cache and handler metrics — this will need to migrate to the `/admin/metrics` endpoint or OpenTelemetry.
93-
- **Metrics migration:** Dropwizard metrics replaced by OpenTelemetry. All metric names switch to snake_case. JMX, Prometheus exporter, SLF4J, and Graphite reporters are removed.
94-
- **SolrJ base URL:** SolrClient now only accepts root URLs (e.g., `http://host:8983/solr`). This project already uses root URLs with per-request collection names, so **no change needed** here.
95-
- **SolrJ dependency:** Upgrade `solr-solrj` from 9.x to 10.x in `gradle/libs.versions.toml`. The Jetty BOM alignment (`jetty = "10.0.22"`) will also need updating since Solr 10 uses Jetty 12.x.
97+
Remaining known differences from Solr 9:
98+
- **`/admin/mbeans` removed:** Cache and handler stats from `getCollectionStats()` will always be `null` on Solr 10. A future migration to `/admin/metrics` will restore these metrics.
99+
- **Metrics migration:** Dropwizard metrics replaced by OpenTelemetry. Metric names switch to snake_case in Solr 10.
100+
- **SolrJ base URL:** Already uses root URLs — **no change needed**.
101+
- **SolrJ 10.x dependency:** Not yet on Maven Central (as of 2026-03-06); tests use SolrJ 9.x against a Solr 10 server. Update `solr-solrj` and Jetty BOM when 10.x is released.
96102

97103
## Key Configuration
98104

build.gradle.kts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,7 @@ dependencies {
9797
implementation(libs.spring.boot.starter.actuator)
9898
implementation(libs.spring.boot.starter.aop)
9999
implementation(libs.spring.ai.starter.mcp.server.webmvc)
100-
implementation(libs.solr.solrj) {
101-
exclude(group = "org.apache.httpcomponents")
102-
}
100+
implementation(libs.solr.solrj)
103101
implementation(libs.commons.csv)
104102
// JSpecify for nullability annotations
105103
implementation(libs.jspecify)
@@ -126,8 +124,6 @@ dependencies {
126124
dependencyManagement {
127125
imports {
128126
mavenBom("org.springframework.ai:spring-ai-bom:${libs.versions.spring.ai.get()}")
129-
// Align Jetty family to 10.x compatible with SolrJ 9.x
130-
mavenBom("org.eclipse.jetty:jetty-bom:${libs.versions.jetty.get()}")
131127
}
132128
}
133129

gradle/libs.versions.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ spotless = "7.0.2"
88

99
# Main dependencies
1010
spring-ai = "1.1.3"
11-
solr = "9.9.0"
11+
solr = "10.0.0"
1212
commons-csv = "1.10.0"
1313
jspecify = "1.0.0"
1414
mcp-server-security = "0.0.4"
@@ -17,8 +17,6 @@ mcp-server-security = "0.0.4"
1717
errorprone-core = "2.38.0"
1818
nullaway = "0.12.7"
1919

20-
# Jetty BOM version
21-
jetty = "10.0.22"
2220

2321
# Test dependencies
2422
testcontainers = "1.21.3"

src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@
2828
import java.util.Date;
2929
import java.util.List;
3030
import org.apache.solr.client.solrj.SolrClient;
31-
import org.apache.solr.client.solrj.SolrQuery;
3231
import org.apache.solr.client.solrj.SolrRequest;
3332
import org.apache.solr.client.solrj.SolrServerException;
3433
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
3534
import org.apache.solr.client.solrj.request.GenericSolrRequest;
3635
import org.apache.solr.client.solrj.request.LukeRequest;
36+
import org.apache.solr.client.solrj.request.SolrQuery;
3737
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
3838
import org.apache.solr.client.solrj.response.LukeResponse;
3939
import org.apache.solr.client.solrj.response.QueryResponse;
@@ -352,7 +352,7 @@ public List<String> listCollections() {
352352
@SuppressWarnings("unchecked")
353353
List<String> collections = (List<String>) response.getResponse().get(COLLECTIONS_KEY);
354354
return collections != null ? collections : new ArrayList<>();
355-
} catch (SolrServerException | IOException e) {
355+
} catch (SolrServerException | IOException _) {
356356
return new ArrayList<>();
357357
}
358358
}
@@ -595,7 +595,9 @@ public CacheStats getCacheMetrics(String collection) {
595595
}
596596

597597
return stats;
598-
} catch (SolrServerException | IOException _) {
598+
} catch (SolrServerException | IOException | RuntimeException _) {
599+
// RuntimeException covers SolrException subclasses (e.g. RemoteSolrException)
600+
// thrown when the /admin/mbeans endpoint is unavailable (removed in Solr 10).
599601
return null; // Return null instead of empty object
600602
}
601603
}
@@ -767,7 +769,9 @@ public HandlerStats getHandlerMetrics(String collection) {
767769
}
768770

769771
return stats;
770-
} catch (SolrServerException | IOException _) {
772+
} catch (SolrServerException | IOException | RuntimeException _) {
773+
// RuntimeException covers SolrException subclasses (e.g. RemoteSolrException)
774+
// thrown when the /admin/mbeans endpoint is unavailable (removed in Solr 10).
771775
return null; // Return null instead of empty object
772776
}
773777
}

src/main/java/org/apache/solr/mcp/server/config/JsonResponseParser.java

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@
2020
import com.fasterxml.jackson.databind.ObjectMapper;
2121
import java.io.IOException;
2222
import java.io.InputStream;
23-
import java.io.Reader;
2423
import java.util.ArrayList;
24+
import java.util.Collection;
2525
import java.util.List;
26-
import org.apache.solr.client.solrj.ResponseParser;
26+
import org.apache.solr.client.solrj.response.ResponseParser;
2727
import org.apache.solr.common.SolrDocument;
2828
import org.apache.solr.common.SolrDocumentList;
2929
import org.apache.solr.common.SolrException;
@@ -80,8 +80,8 @@ public String getWriterType() {
8080
}
8181

8282
@Override
83-
public String getContentType() {
84-
return MediaType.APPLICATION_JSON_VALUE;
83+
public Collection<String> getContentTypes() {
84+
return List.of(MediaType.APPLICATION_JSON_VALUE);
8585
}
8686

8787
@Override
@@ -93,15 +93,6 @@ public NamedList<Object> processResponse(InputStream body, String encoding) {
9393
}
9494
}
9595

96-
@Override
97-
public NamedList<Object> processResponse(Reader reader) {
98-
try {
99-
return toNamedList(mapper.readTree(reader));
100-
} catch (IOException e) {
101-
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Failed to parse Solr JSON response", e);
102-
}
103-
}
104-
10596
private SimpleOrderedMap<Object> toNamedList(JsonNode objectNode) {
10697
SimpleOrderedMap<Object> result = new SimpleOrderedMap<>();
10798
objectNode.fields().forEachRemaining(entry -> result.add(entry.getKey(), convertValue(entry.getValue())));

src/main/java/org/apache/solr/mcp/server/config/SolrConfig.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import com.fasterxml.jackson.databind.ObjectMapper;
2020
import java.util.concurrent.TimeUnit;
2121
import org.apache.solr.client.solrj.SolrClient;
22-
import org.apache.solr.client.solrj.impl.Http2SolrClient;
22+
import org.apache.solr.client.solrj.impl.HttpJdkSolrClient;
2323
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2424
import org.springframework.context.annotation.Bean;
2525
import org.springframework.context.annotation.Configuration;
@@ -94,7 +94,7 @@
9494
* </ul>
9595
*
9696
* @see SolrConfigurationProperties
97-
* @see Http2SolrClient
97+
* @see HttpJdkSolrClient
9898
* @see org.springframework.boot.context.properties.EnableConfigurationProperties
9999
*/
100100
@Configuration
@@ -162,7 +162,7 @@ public class SolrConfig {
162162
* the injected Solr configuration properties containing connection
163163
* URL
164164
* @return configured SolrClient instance ready for use in application services
165-
* @see Http2SolrClient.Builder
165+
* @see HttpJdkSolrClient.Builder
166166
* @see SolrConfigurationProperties#url()
167167
*/
168168
@Bean
@@ -190,7 +190,7 @@ SolrClient solrClient(SolrConfigurationProperties properties, JsonResponseParser
190190
}
191191

192192
// Use with explicit base URL; JSON wire format replaces the JavaBin default
193-
return new Http2SolrClient.Builder(url).withConnectionTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)
193+
return new HttpJdkSolrClient.Builder(url).withConnectionTimeout(CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)
194194
.withIdleTimeout(SOCKET_TIMEOUT_MS, TimeUnit.MILLISECONDS).withResponseParser(jsonResponseParser)
195195
.build();
196196
}

src/main/java/org/apache/solr/mcp/server/search/SearchService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
import java.util.List;
2424
import java.util.Map;
2525
import org.apache.solr.client.solrj.SolrClient;
26-
import org.apache.solr.client.solrj.SolrQuery;
2726
import org.apache.solr.client.solrj.SolrServerException;
27+
import org.apache.solr.client.solrj.request.SolrQuery;
2828
import org.apache.solr.client.solrj.response.FacetField;
2929
import org.apache.solr.client.solrj.response.QueryResponse;
3030
import org.apache.solr.common.SolrDocument;

src/test/java/org/apache/solr/mcp/server/config/SolrConfigTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import static org.junit.jupiter.api.Assertions.*;
2020

2121
import org.apache.solr.client.solrj.SolrClient;
22-
import org.apache.solr.client.solrj.impl.Http2SolrClient;
22+
import org.apache.solr.client.solrj.impl.HttpJdkSolrClient;
2323
import org.apache.solr.mcp.server.TestcontainersConfiguration;
2424
import org.junit.jupiter.api.Test;
2525
import org.springframework.beans.factory.annotation.Autowired;
@@ -49,9 +49,9 @@ void testSolrClientConfiguration() {
4949

5050
// Verify that the SolrClient is using the correct URL
5151
// Note: SolrConfig normalizes the URL to have trailing slash, but
52-
// Http2SolrClient removes
52+
// HttpJdkSolrClient removes
5353
// it
54-
var httpSolrClient = assertInstanceOf(Http2SolrClient.class, solrClient);
54+
var httpSolrClient = assertInstanceOf(HttpJdkSolrClient.class, solrClient);
5555
String expectedUrl = "http://" + solrContainer.getHost() + ":" + solrContainer.getMappedPort(8983) + "/solr";
5656
assertEquals(expectedUrl, httpSolrClient.getBaseURL());
5757
}

src/test/java/org/apache/solr/mcp/server/config/SolrConfigUrlNormalizationTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
import com.fasterxml.jackson.databind.ObjectMapper;
2222
import org.apache.solr.client.solrj.SolrClient;
23-
import org.apache.solr.client.solrj.impl.Http2SolrClient;
23+
import org.apache.solr.client.solrj.impl.HttpJdkSolrClient;
2424
import org.junit.jupiter.params.ParameterizedTest;
2525
import org.junit.jupiter.params.provider.CsvSource;
2626
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,7 +45,7 @@ void testUrlNormalization(String inputUrl, String expectedUrl) throws Exception
4545
try (SolrClient client = solrConfig.solrClient(testProperties, new JsonResponseParser(objectMapper))) {
4646
assertNotNull(client);
4747

48-
var httpClient = assertInstanceOf(Http2SolrClient.class, client);
48+
var httpClient = assertInstanceOf(HttpJdkSolrClient.class, client);
4949
assertEquals(expectedUrl, httpClient.getBaseURL());
5050
}
5151
}

0 commit comments

Comments
 (0)