This is an automated email from the ASF dual-hosted git repository.

epugh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-mcp.git


The following commit(s) were added to refs/heads/main by this push:
     new 412a5350 fix: remove unreachable null checks and declare real 
nullability (#267)
412a5350 is described below

commit 412a53505edee02da725c3e52a6031e7c4a2ce4c
Author: Aditya Parikh <[email protected]>
AuthorDate: Mon Oct 26 15:15:58 2026 -0400

    fix: remove unreachable null checks and declare real nullability (#267)
    
    IntelliJ reported conditions that can never be true and null flows that
    the declared types did not allow. Each one was either a null check on an
    input that cannot be null, or a type that claimed non-null for a value
    that can be null. They are fixed in that order:
    
    - Null checks on required @McpToolParam/@McpArg parameters are removed.
      MCP SDK 2.0 validates tool arguments against the input schema before
      dispatch, and CompleteRequest/CompleteArgument reject a null argument
      or value in their constructors. The blank and empty checks stay.
    - Values that really can be null keep their checks and get the type that
      says so: optional prompt arguments (purpose, sample, sampleDocument),
      PromptText.optionalCodeBlock's content, missing NamedList entries
      (CollectionUtils getters take NamedList<?>), JSON nulls in
      JsonResponseParser's containers, YAML front-matter nulls in
      MarkdownDocumentCreator.addValue, and maxScore when scores were not
      requested.
    - SortClause.order is @Nullable. The search tool documents it as
      optional, defaulting to asc, but the generated schema listed it as
      required, so a clause without an order was rejected before reaching
      the code that applies the default. Now only field is required.
    
    Tests that passed null to now-required parameters are removed (10). The
    two that pin a contract on purpose (the SDK rejecting a null completion
    value, the binder passing null for a missing solr.url) keep the null and
    suppress the warning. Other test fixes: @Nullable on captured system
    properties, AssertJ isPositive() instead of unboxing nullable counters,
    @AutoClose for the MCP client, assertions that were always true
    (required() || !required(), size() >= 0, non-null span timestamps) now
    check what their messages describe, and unused fields, mocks, throws
    clauses and an empty else are gone.
    
    Signed-off-by: Aditya Parikh <[email protected]>
    Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
 .../solr/mcp/server/collection/AliasService.java   |  6 +-
 .../mcp/server/collection/CollectionService.java   | 29 ++++-----
 .../mcp/server/collection/CollectionUtils.java     |  6 +-
 .../apache/solr/mcp/server/collection/Dtos.java    |  2 +-
 .../solr/mcp/server/config/JsonResponseParser.java | 14 ++---
 .../solr/mcp/server/indexing/IndexingService.java  |  8 +--
 .../documentcreator/IndexingDocumentCreator.java   |  4 +-
 .../documentcreator/JsonDocumentCreator.java       |  2 +-
 .../documentcreator/MarkdownDocumentCreator.java   |  2 +-
 .../solr/mcp/server/schema/SchemaService.java      |  7 ++-
 .../solr/mcp/server/search/SearchResponse.java     |  3 +-
 .../solr/mcp/server/search/SearchService.java      |  2 +-
 .../apache/solr/mcp/server/search/SortClause.java  |  3 +-
 .../server/security/HttpSecurityConfiguration.java |  4 +-
 .../apache/solr/mcp/server/util/PromptText.java    |  5 +-
 .../solr/mcp/server/BuildInfoReaderTest.java       |  3 +-
 .../solr/mcp/server/LoggingConfigurationTest.java  |  3 +-
 .../mcp/server/McpClientIntegrationTestBase.java   | 43 ++++++--------
 .../solr/mcp/server/McpToolRegistrationTest.java   |  6 +-
 .../solr/mcp/server/SampleDataRuntimeHints.java    |  2 +-
 .../mcp/server/collection/AliasServiceTest.java    | 24 --------
 .../CollectionServiceIntegrationTest.java          | 68 ++++++++++++----------
 .../server/collection/CollectionServiceTest.java   | 32 ++--------
 .../mcp/server/collection/CollectionUtilsTest.java | 21 ++++---
 .../config/SolrConfigurationPropertiesTest.java    |  1 +
 .../DockerImageHttpIntegrationTest.java            | 19 +++---
 .../indexing/IndexingServiceIntegrationTest.java   | 13 ++---
 .../mcp/server/indexing/IndexingServiceTest.java   |  9 ---
 .../mcp/server/indexing/MarkdownIndexingTest.java  | 22 +++----
 .../documentcreator/JsonDocumentCreatorTest.java   | 14 +----
 .../observability/DistributedTracingTest.java      |  8 +--
 .../mcp/server/observability/TraceAssertions.java  |  3 +-
 .../schema/SchemaServiceIntegrationTest.java       | 18 +-----
 .../solr/mcp/server/schema/SchemaServiceTest.java  | 37 ------------
 .../search/SearchServiceIntegrationTest.java       |  7 +--
 .../solr/mcp/server/search/SearchServiceTest.java  | 24 +++-----
 .../solr/mcp/server/search/SortClauseTest.java     |  2 -
 37 files changed, 172 insertions(+), 304 deletions(-)

diff --git 
a/src/main/java/org/apache/solr/mcp/server/collection/AliasService.java 
b/src/main/java/org/apache/solr/mcp/server/collection/AliasService.java
index a930b70b..962124f9 100644
--- a/src/main/java/org/apache/solr/mcp/server/collection/AliasService.java
+++ b/src/main/java/org/apache/solr/mcp/server/collection/AliasService.java
@@ -130,10 +130,10 @@ public class AliasService {
           String collections)
       throws SolrServerException, IOException {
 
-    if (aliasName == null || aliasName.isBlank()) {
+    if (aliasName.isBlank()) {
       throw new IllegalArgumentException(BLANK_ALIAS_NAME_ERROR);
     }
-    if (collections == null || collections.isBlank()) {
+    if (collections.isBlank()) {
       throw new IllegalArgumentException(BLANK_COLLECTIONS_ERROR);
     }
 
@@ -176,7 +176,7 @@ public class AliasService {
       @McpToolParam(description = "Name of the alias to delete") String 
aliasName)
       throws SolrServerException, IOException {
 
-    if (aliasName == null || aliasName.isBlank()) {
+    if (aliasName.isBlank()) {
       throw new IllegalArgumentException(BLANK_ALIAS_NAME_ERROR);
     }
 
diff --git 
a/src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java 
b/src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java
index 057ede94..aac8d277 100644
--- a/src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java
+++ b/src/main/java/org/apache/solr/mcp/server/collection/CollectionService.java
@@ -135,9 +135,6 @@ public class CollectionService {
   /** Universal Solr query pattern to match all documents in a collection */
   private static final String ALL_DOCUMENTS_QUERY = "*:*";
 
-  /** Suffix pattern used to identify shard names in SolrCloud deployments */
-  private static final String SHARD_SUFFIX = "_shard";
-
   /**
    * Matches a SolrCloud shard/replica suffix at the end of a core name, e.g. 
{@code _shard1} or
    * {@code _shard1_replica_n1}. Anchored so that collection names merely 
containing "_shard" are
@@ -338,13 +335,13 @@ public class CollectionService {
   @PreAuthorize("isAuthenticated()")
   @McpComplete(uri = "solr://{collection}/schema")
   public List<String> completeCollection(CompleteRequest.CompleteArgument 
argument) {
-    if (argument == null || !"collection".equals(argument.name())) {
+    if (!"collection".equals(argument.name())) {
       return List.of();
     }
-    String prefix = argument.value() == null ? "" : 
argument.value().toLowerCase(Locale.ROOT);
+    String prefix = argument.value().toLowerCase(Locale.ROOT);
     try {
       return listCollections().stream()
-          .filter(c -> c != null && 
c.toLowerCase(Locale.ROOT).startsWith(prefix))
+          .filter(c -> c.toLowerCase(Locale.ROOT).startsWith(prefix))
           .sorted()
           .limit(MAX_COMPLETION_RESULTS)
           .toList();
@@ -675,7 +672,7 @@ public class CollectionService {
   }
 
   @SuppressWarnings("unchecked")
-  private @Nullable CacheInfo extractSingleCacheInfo(NamedList<Object> 
coreMetrics, String key) {
+  private @Nullable CacheInfo extractSingleCacheInfo(NamedList<?> coreMetrics, 
String key) {
     NamedList<Object> cache = (NamedList<Object>) coreMetrics.get(key);
     if (cache == null) {
       return null;
@@ -752,10 +749,10 @@ public class CollectionService {
    * collection is not enabled.
    *
    * @param stats the handler statistics to evaluate
-   * @return true if the stats are null or all handler types are null
+   * @return true if all handler types are null
    */
   private boolean isHandlerStatsEmpty(HandlerStats stats) {
-    return stats == null || (stats.selectHandler() == null && 
stats.updateHandler() == null);
+    return stats.selectHandler() == null && stats.updateHandler() == null;
   }
 
   /**
@@ -776,14 +773,14 @@ public class CollectionService {
     GenericSolrRequest request =
         new GenericSolrRequest(SolrRequest.METHOD.GET, ADMIN_METRICS_PATH, 
params);
 
-    NamedList<Object> response;
+    NamedList<?> response;
     try {
       response = solrClient.request(request);
     } catch (SolrServerException | IOException | SolrException e) {
       logger.debug("Cache/handler metrics unavailable for collection: {}", 
collection, e);
       return null;
     }
-    NamedList<Object> metrics = (NamedList<Object>) response.get(METRICS_KEY);
+    NamedList<?> metrics = (NamedList<?>) response.get(METRICS_KEY);
     if (metrics == null || metrics.size() == 0) {
       return null;
     }
@@ -833,7 +830,7 @@ public class CollectionService {
    * <p><strong>Extraction Logic:</strong>
    *
    * <ul>
-   *   <li>Detects shard patterns containing the {@value #SHARD_SUFFIX} suffix
+   *   <li>Detects a trailing {@code _shard<N>} or {@code 
_shard<N>_replica...} suffix
    *   <li>Returns the substring before the shard identifier
    *   <li>Returns the original string if no shard pattern is detected
    * </ul>
@@ -850,10 +847,6 @@ public class CollectionService {
    * @return the extracted collection name, or the original string if no shard 
pattern found
    */
   String extractCollectionName(String collectionOrShard) {
-    if (collectionOrShard == null || collectionOrShard.isEmpty()) {
-      return collectionOrShard;
-    }
-
     // Strip only a real SolrCloud shard/replica suffix. Matching a bare
     // "_shard" anywhere would truncate legitimate collection names such as
     // "orders_shard_archive" down to "orders".
@@ -965,7 +958,7 @@ public class CollectionService {
           @Nullable Integer replicationFactor)
       throws SolrServerException, IOException {
 
-    if (name == null || name.isBlank()) {
+    if (name.isBlank()) {
       throw new IllegalArgumentException(BLANK_COLLECTION_NAME_ERROR);
     }
 
@@ -1045,7 +1038,7 @@ public class CollectionService {
               description =
                   "Optional one-line description of what the collection is for 
(used only to ground the conversation).",
               required = false)
-          String purpose) {
+          @Nullable String purpose) {
     String purposeLine =
         (purpose == null || purpose.isBlank()) ? "" : "\nPurpose: 
%s\n".formatted(purpose.strip());
     return """
diff --git 
a/src/main/java/org/apache/solr/mcp/server/collection/CollectionUtils.java 
b/src/main/java/org/apache/solr/mcp/server/collection/CollectionUtils.java
index 2635633b..19128668 100644
--- a/src/main/java/org/apache/solr/mcp/server/collection/CollectionUtils.java
+++ b/src/main/java/org/apache/solr/mcp/server/collection/CollectionUtils.java
@@ -93,7 +93,7 @@ public class CollectionUtils {
    * @see Number#longValue()
    * @see Long#parseLong(String)
    */
-  public static @Nullable Long getLong(NamedList<Object> response, String key) 
{
+  public static @Nullable Long getLong(NamedList<?> response, String key) {
     Object value = response.get(key);
     if (value == null) return null;
 
@@ -139,7 +139,7 @@ public class CollectionUtils {
    * @return the Float value if found and convertible, {@code null} otherwise
    * @see Number#floatValue()
    */
-  public static @Nullable Float getFloat(NamedList<Object> stats, String key) {
+  public static @Nullable Float getFloat(NamedList<?> stats, String key) {
     Object value = stats.get(key);
     if (value == null) return null;
 
@@ -200,7 +200,7 @@ public class CollectionUtils {
    * @see Integer#parseInt(String)
    * @see #getLong(NamedList, String)
    */
-  public static @Nullable Integer getInteger(NamedList<Object> response, 
String key) {
+  public static @Nullable Integer getInteger(NamedList<?> response, String 
key) {
     Object value = response.get(key);
     if (value == null) return null;
 
diff --git a/src/main/java/org/apache/solr/mcp/server/collection/Dtos.java 
b/src/main/java/org/apache/solr/mcp/server/collection/Dtos.java
index dddc3fd5..3c389089 100644
--- a/src/main/java/org/apache/solr/mcp/server/collection/Dtos.java
+++ b/src/main/java/org/apache/solr/mcp/server/collection/Dtos.java
@@ -157,7 +157,7 @@ record QueryStats(
     Long start,
 
     /** Highest relevance score among the returned documents */
-    Float maxScore) {}
+    @Nullable Float maxScore) {}
 
 /**
  * Solr cache utilization statistics across all cache types.
diff --git 
a/src/main/java/org/apache/solr/mcp/server/config/JsonResponseParser.java 
b/src/main/java/org/apache/solr/mcp/server/config/JsonResponseParser.java
index 49f77f0b..79c440c4 100644
--- a/src/main/java/org/apache/solr/mcp/server/config/JsonResponseParser.java
+++ b/src/main/java/org/apache/solr/mcp/server/config/JsonResponseParser.java
@@ -90,7 +90,7 @@ class JsonResponseParser extends ResponseParser {
   private static final String FACET_FIELDS_PATH = "facet_counts/facet_fields";
 
   @Override
-  public NamedList<Object> processResponse(InputStream body, String encoding) {
+  public NamedList<@Nullable Object> processResponse(InputStream body, String 
encoding) {
     try {
       return toNamedList(mapper.readTree(body), "");
     } catch (JacksonException e) {
@@ -99,8 +99,8 @@ class JsonResponseParser extends ResponseParser {
     }
   }
 
-  private SimpleOrderedMap<Object> toNamedList(JsonNode objectNode, String 
path) {
-    SimpleOrderedMap<Object> result = new SimpleOrderedMap<>();
+  private SimpleOrderedMap<@Nullable Object> toNamedList(JsonNode objectNode, 
String path) {
+    SimpleOrderedMap<@Nullable Object> result = new SimpleOrderedMap<>();
     objectNode
         .properties()
         .forEach(
@@ -150,7 +150,7 @@ class JsonResponseParser extends ResponseParser {
     if (isFlatNamedList(arrayNode)) {
       return flatArrayToNamedList(arrayNode);
     }
-    List<Object> list = new ArrayList<>(arrayNode.size());
+    List<@Nullable Object> list = new ArrayList<>(arrayNode.size());
     arrayNode.forEach(element -> list.add(convertValue(element, path)));
     return list;
   }
@@ -177,8 +177,8 @@ class JsonResponseParser extends ResponseParser {
     return true;
   }
 
-  private SimpleOrderedMap<Object> flatArrayToNamedList(JsonNode arrayNode) {
-    SimpleOrderedMap<Object> result = new SimpleOrderedMap<>();
+  private SimpleOrderedMap<@Nullable Object> flatArrayToNamedList(JsonNode 
arrayNode) {
+    SimpleOrderedMap<@Nullable Object> result = new SimpleOrderedMap<>();
     for (int i = 0; i < arrayNode.size(); i += 2) {
       // Values here are facet counts (scalars), never nested facet arrays.
       result.add(arrayNode.get(i).textValue(), convertValue(arrayNode.get(i + 
1), ""));
@@ -210,7 +210,7 @@ class JsonResponseParser extends ResponseParser {
               JsonNode val = entry.getValue();
               if (val.isArray()) {
                 // Multi-valued field — always a plain list, never a flat 
NamedList
-                List<Object> values = new ArrayList<>(val.size());
+                List<@Nullable Object> values = new ArrayList<>(val.size());
                 val.forEach(v -> values.add(convertValue(v, "")));
                 doc.setField(entry.getKey(), values);
               } else {
diff --git 
a/src/main/java/org/apache/solr/mcp/server/indexing/IndexingService.java 
b/src/main/java/org/apache/solr/mcp/server/indexing/IndexingService.java
index 5fd8c483..699f643a 100644
--- a/src/main/java/org/apache/solr/mcp/server/indexing/IndexingService.java
+++ b/src/main/java/org/apache/solr/mcp/server/indexing/IndexingService.java
@@ -37,6 +37,7 @@ import org.apache.solr.common.util.ContentStreamBase;
 import 
org.apache.solr.mcp.server.indexing.documentcreator.IndexingDocumentCreator;
 import org.apache.solr.mcp.server.util.PromptNames;
 import org.apache.solr.mcp.server.util.PromptText;
+import org.jspecify.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.ai.mcp.annotation.McpArg;
@@ -355,9 +356,6 @@ public class IndexingService {
                   "Markdown documents to index, one string per document, each 
optionally starting with YAML front matter")
           List<String> documents)
       throws IOException, SolrServerException {
-    if (documents == null) {
-      throw new IllegalArgumentException("documents cannot be null");
-    }
     List<SolrInputDocument> schemalessDoc = new ArrayList<>();
     for (String markdown : documents) {
       
schemalessDoc.addAll(indexingDocumentCreator.createSchemalessDocumentsFromMarkdown(markdown));
@@ -461,7 +459,7 @@ public class IndexingService {
   private record IndexTool(String format, String name, String paramName, 
String payload) {}
 
   private static IndexTool resolveIndexTool(String format) {
-    String normalized = (format == null) ? "" : format.trim().toLowerCase();
+    String normalized = format.trim().toLowerCase();
     return switch (normalized) {
       case "json" ->
           new IndexTool(
@@ -512,7 +510,7 @@ public class IndexingService {
               description =
                   "Optional small sample of the input document(s) to ground 
field-shape decisions",
               required = false)
-          String sample) {
+          @Nullable String sample) {
     IndexTool indexTool = resolveIndexTool(format);
     String sampleSection =
         PromptText.optionalCodeBlock(
diff --git 
a/src/main/java/org/apache/solr/mcp/server/indexing/documentcreator/IndexingDocumentCreator.java
 
b/src/main/java/org/apache/solr/mcp/server/indexing/documentcreator/IndexingDocumentCreator.java
index b0652e0c..5f66b075 100644
--- 
a/src/main/java/org/apache/solr/mcp/server/indexing/documentcreator/IndexingDocumentCreator.java
+++ 
b/src/main/java/org/apache/solr/mcp/server/indexing/documentcreator/IndexingDocumentCreator.java
@@ -105,8 +105,8 @@ public class IndexingDocumentCreator {
       throws DocumentProcessingException {
 
     // Input validation
-    if (markdown == null || markdown.trim().isEmpty()) {
-      throw new DocumentProcessingException("Markdown input cannot be null or 
empty");
+    if (markdown.trim().isEmpty()) {
+      throw new DocumentProcessingException("Markdown input cannot be empty");
     }
 
     return markdownDocumentCreator.create(markdown);
diff --git 
a/src/main/java/org/apache/solr/mcp/server/indexing/documentcreator/JsonDocumentCreator.java
 
b/src/main/java/org/apache/solr/mcp/server/indexing/documentcreator/JsonDocumentCreator.java
index 5754ff38..62080b8c 100644
--- 
a/src/main/java/org/apache/solr/mcp/server/indexing/documentcreator/JsonDocumentCreator.java
+++ 
b/src/main/java/org/apache/solr/mcp/server/indexing/documentcreator/JsonDocumentCreator.java
@@ -120,7 +120,7 @@ public class JsonDocumentCreator implements 
SolrDocumentCreator {
    */
   public List<SolrInputDocument> create(List<Map<String, Object>> documents)
       throws DocumentProcessingException {
-    if (documents == null || documents.isEmpty()) {
+    if (documents.isEmpty()) {
       throw new DocumentProcessingException("JSON input cannot be empty");
     }
     return flatten(objectMapper.valueToTree(documents));
diff --git 
a/src/main/java/org/apache/solr/mcp/server/indexing/documentcreator/MarkdownDocumentCreator.java
 
b/src/main/java/org/apache/solr/mcp/server/indexing/documentcreator/MarkdownDocumentCreator.java
index 3e84db54..bff112b7 100644
--- 
a/src/main/java/org/apache/solr/mcp/server/indexing/documentcreator/MarkdownDocumentCreator.java
+++ 
b/src/main/java/org/apache/solr/mcp/server/indexing/documentcreator/MarkdownDocumentCreator.java
@@ -309,7 +309,7 @@ public class MarkdownDocumentCreator implements 
SolrDocumentCreator {
     block.unlink();
   }
 
-  private static void addValue(SolrInputDocument doc, String fieldName, Object 
value) {
+  private static void addValue(SolrInputDocument doc, String fieldName, 
@Nullable Object value) {
     switch (value) {
       case null -> {}
       case Map<?, ?> nested ->
diff --git a/src/main/java/org/apache/solr/mcp/server/schema/SchemaService.java 
b/src/main/java/org/apache/solr/mcp/server/schema/SchemaService.java
index 7e738aa7..cda33227 100644
--- a/src/main/java/org/apache/solr/mcp/server/schema/SchemaService.java
+++ b/src/main/java/org/apache/solr/mcp/server/schema/SchemaService.java
@@ -32,6 +32,7 @@ import 
org.apache.solr.client.solrj.request.schema.FieldTypeDefinition;
 import org.apache.solr.client.solrj.request.schema.SchemaRequest;
 import org.apache.solr.client.solrj.response.schema.SchemaRepresentation;
 import org.apache.solr.mcp.server.util.PromptNames;
+import org.jspecify.annotations.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.ai.mcp.annotation.McpArg;
@@ -429,13 +430,13 @@ public class SchemaService {
   }
 
   private static void requireCollection(String collection) {
-    if (collection == null || collection.isBlank()) {
+    if (collection.isBlank()) {
       throw new IllegalArgumentException("Collection name must not be blank");
     }
   }
 
   private static void requireNonEmpty(List<?> list, String name) {
-    if (list == null || list.isEmpty()) {
+    if (list.isEmpty()) {
       throw new IllegalArgumentException(name + " must not be empty");
     }
   }
@@ -521,7 +522,7 @@ public class SchemaService {
               name = "sampleDocument",
               description = "Optional single document in JSON to ground field 
inference",
               required = false)
-          String sampleDocument) {
+          @Nullable String sampleDocument) {
     String sampleSection =
         optionalCodeBlock(
             sampleDocument,
diff --git 
a/src/main/java/org/apache/solr/mcp/server/search/SearchResponse.java 
b/src/main/java/org/apache/solr/mcp/server/search/SearchResponse.java
index 13f95302..e05277fd 100644
--- a/src/main/java/org/apache/solr/mcp/server/search/SearchResponse.java
+++ b/src/main/java/org/apache/solr/mcp/server/search/SearchResponse.java
@@ -18,6 +18,7 @@ package org.apache.solr.mcp.server.search;
 
 import java.util.List;
 import java.util.Map;
+import org.jspecify.annotations.Nullable;
 
 /**
  * Immutable record representing a structured search response from Apache Solr 
operations.
@@ -109,6 +110,6 @@ import java.util.Map;
 public record SearchResponse(
     long numFound,
     long start,
-    Float maxScore,
+    @Nullable Float maxScore,
     List<Map<String, Object>> documents,
     Map<String, Map<String, Long>> facets) {}
diff --git a/src/main/java/org/apache/solr/mcp/server/search/SearchService.java 
b/src/main/java/org/apache/solr/mcp/server/search/SearchService.java
index 849ede71..9fdf61ff 100644
--- a/src/main/java/org/apache/solr/mcp/server/search/SearchService.java
+++ b/src/main/java/org/apache/solr/mcp/server/search/SearchService.java
@@ -295,7 +295,7 @@ public class SearchService {
     // sorting
     if (!CollectionUtils.isEmpty(sortClauses)) {
       sortClauses.stream()
-          .filter(clause -> clause != null && 
StringUtils.hasText(clause.field()))
+          .filter(clause -> StringUtils.hasText(clause.field()))
           .map(SortClause::toSolrSortClause)
           .forEach(solrQuery::addSort);
     }
diff --git a/src/main/java/org/apache/solr/mcp/server/search/SortClause.java 
b/src/main/java/org/apache/solr/mcp/server/search/SortClause.java
index 3cb70ef8..c5f889e4 100644
--- a/src/main/java/org/apache/solr/mcp/server/search/SortClause.java
+++ b/src/main/java/org/apache/solr/mcp/server/search/SortClause.java
@@ -18,6 +18,7 @@ package org.apache.solr.mcp.server.search;
 
 import java.util.Locale;
 import org.apache.solr.client.solrj.request.SolrQuery;
+import org.jspecify.annotations.Nullable;
 import org.springframework.util.StringUtils;
 
 /**
@@ -31,7 +32,7 @@ import org.springframework.util.StringUtils;
  * @param order the sort direction, {@code asc} or {@code desc} (case 
insensitive); defaults to
  *     {@code asc} when omitted
  */
-public record SortClause(String field, String order) {
+public record SortClause(String field, @Nullable String order) {
 
   /** Default sort order applied when {@code order} is omitted. */
   private static final String DEFAULT_ORDER = "asc";
diff --git 
a/src/main/java/org/apache/solr/mcp/server/security/HttpSecurityConfiguration.java
 
b/src/main/java/org/apache/solr/mcp/server/security/HttpSecurityConfiguration.java
index 890da624..ca43fa78 100644
--- 
a/src/main/java/org/apache/solr/mcp/server/security/HttpSecurityConfiguration.java
+++ 
b/src/main/java/org/apache/solr/mcp/server/security/HttpSecurityConfiguration.java
@@ -48,7 +48,7 @@ class HttpSecurityConfiguration {
       name = "http.security.enabled",
       havingValue = "true",
       matchIfMissing = true)
-  SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+  SecurityFilterChain securityFilterChain(HttpSecurity http) {
     http.authorizeHttpRequests(
         auth -> {
           // Liveness/readiness probes need anonymous access for load
@@ -104,7 +104,7 @@ class HttpSecurityConfiguration {
 
   @Bean
   @ConditionalOnProperty(name = "http.security.enabled", havingValue = "false")
-  SecurityFilterChain unsecured(HttpSecurity http) throws Exception {
+  SecurityFilterChain unsecured(HttpSecurity http) {
     return http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
         // MCP inspector
         .cors(cors -> cors.configurationSource(corsConfigurationSource()))
diff --git a/src/main/java/org/apache/solr/mcp/server/util/PromptText.java 
b/src/main/java/org/apache/solr/mcp/server/util/PromptText.java
index c53f1e9c..350b6d93 100644
--- a/src/main/java/org/apache/solr/mcp/server/util/PromptText.java
+++ b/src/main/java/org/apache/solr/mcp/server/util/PromptText.java
@@ -16,6 +16,8 @@
  */
 package org.apache.solr.mcp.server.util;
 
+import org.jspecify.annotations.Nullable;
+
 /** Shared text-shaping helpers for {@code @McpPrompt} method bodies. */
 public final class PromptText {
 
@@ -33,7 +35,8 @@ public final class PromptText {
    * @param absentLine the full bullet line for the "no content" case
    * @return a string ready to be interpolated into a Java text block
    */
-  public static String optionalCodeBlock(String content, String presentLead, 
String absentLine) {
+  public static String optionalCodeBlock(
+      @Nullable String content, String presentLead, String absentLine) {
     if (content == null || content.isBlank()) {
       return "   - " + absentLine;
     }
diff --git a/src/test/java/org/apache/solr/mcp/server/BuildInfoReaderTest.java 
b/src/test/java/org/apache/solr/mcp/server/BuildInfoReaderTest.java
index dd57e2f1..e1049d2a 100644
--- a/src/test/java/org/apache/solr/mcp/server/BuildInfoReaderTest.java
+++ b/src/test/java/org/apache/solr/mcp/server/BuildInfoReaderTest.java
@@ -18,6 +18,7 @@ package org.apache.solr.mcp.server;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
+import org.jspecify.annotations.Nullable;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -34,7 +35,7 @@ class BuildInfoReaderTest {
 
   private static final String SUFFIX_PROPERTY = 
"solr.mcp.docker.image.tag.suffix";
 
-  private String previousSuffix;
+  private @Nullable String previousSuffix;
 
   @BeforeEach
   void captureSuffixProperty() {
diff --git 
a/src/test/java/org/apache/solr/mcp/server/LoggingConfigurationTest.java 
b/src/test/java/org/apache/solr/mcp/server/LoggingConfigurationTest.java
index 149d0810..97fb97bd 100644
--- a/src/test/java/org/apache/solr/mcp/server/LoggingConfigurationTest.java
+++ b/src/test/java/org/apache/solr/mcp/server/LoggingConfigurationTest.java
@@ -26,6 +26,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.UncheckedIOException;
 import java.util.Properties;
+import org.jspecify.annotations.Nullable;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -63,7 +64,7 @@ class LoggingConfigurationTest {
 
   private static final String SPRING_VARIANT = "logback-spring.xml";
 
-  private String previousStatusListenerClass;
+  private @Nullable String previousStatusListenerClass;
 
   @BeforeEach
   void rememberStatusListenerProperty() {
diff --git 
a/src/test/java/org/apache/solr/mcp/server/McpClientIntegrationTestBase.java 
b/src/test/java/org/apache/solr/mcp/server/McpClientIntegrationTestBase.java
index 912a4bca..bacd8506 100644
--- a/src/test/java/org/apache/solr/mcp/server/McpClientIntegrationTestBase.java
+++ b/src/test/java/org/apache/solr/mcp/server/McpClientIntegrationTestBase.java
@@ -38,7 +38,7 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.function.Function;
 import java.util.stream.Collectors;
-import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AutoClose;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.MethodOrderer;
 import org.junit.jupiter.api.Order;
@@ -71,6 +71,8 @@ public abstract class McpClientIntegrationTestBase {
 
   protected static final ObjectMapper OBJECT_MAPPER = 
JsonMapper.builder().build();
 
+  @AutoClose
+  @SuppressWarnings("NotNullFieldNotInitialized") // set by beforeAll()
   protected McpSyncClient mcpClient;
 
   protected abstract McpSyncClient createClient() throws Exception;
@@ -81,13 +83,6 @@ public abstract class McpClientIntegrationTestBase {
     mcpClient.initialize();
   }
 
-  @AfterAll
-  void afterAll() {
-    if (mcpClient != null) {
-      mcpClient.close();
-    }
-  }
-
   @Test
   @Order(1)
   void pingServer() {
@@ -234,7 +229,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(7)
-  void searchAllDocuments() throws Exception {
+  void searchAllDocuments() {
     CallToolResult result =
         mcpClient.callTool(
             new CallToolRequest(
@@ -250,7 +245,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(8)
-  void searchWithFilterQuery() throws Exception {
+  void searchWithFilterQuery() {
     CallToolResult result =
         mcpClient.callTool(
             new CallToolRequest(
@@ -273,7 +268,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(9)
-  void searchWithKeyword() throws Exception {
+  void searchWithKeyword() {
     CallToolResult result =
         mcpClient.callTool(
             new CallToolRequest("search", Map.of("collection", COLLECTION, 
"query", "title:Solr")));
@@ -289,7 +284,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(10)
-  void searchWithPagination() throws Exception {
+  void searchWithPagination() {
     CallToolResult page1 =
         mcpClient.callTool(
             new CallToolRequest(
@@ -342,7 +337,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(13)
-  void searchWithFacets() throws Exception {
+  void searchWithFacets() {
     CallToolResult result =
         mcpClient.callTool(
             new CallToolRequest(
@@ -389,7 +384,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(15)
-  void searchFindsAllDocumentsAfterCsvIndexing() throws Exception {
+  void searchFindsAllDocumentsAfterCsvIndexing() {
     CallToolResult result =
         mcpClient.callTool(
             new CallToolRequest(
@@ -402,7 +397,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(16)
-  void addFieldsToTestCollection() throws Exception {
+  void addFieldsToTestCollection() {
     List<Map<String, Object>> fields =
         List.of(
             Map.of(
@@ -476,7 +471,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(18)
-  void searchWithNewFieldFilters() throws Exception {
+  void searchWithNewFieldFilters() {
     CallToolResult byPlatform =
         mcpClient.callTool(
             new CallToolRequest(
@@ -512,7 +507,7 @@ public abstract class McpClientIntegrationTestBase {
   // (the markdown document carries none of the fields its filter queries 
touch).
   @Test
   @Order(18)
-  void indexMarkdownDocumentAndFindItById() throws Exception {
+  void indexMarkdownDocumentAndFindItById() {
     String markdown =
         """
                                ---
@@ -756,7 +751,7 @@ public abstract class McpClientIntegrationTestBase {
         "Result should mention at least one shows-specific field that was 
added: " + text);
   }
 
-  private java.util.Set<String> fetchExistingFieldNames(String collection) 
throws Exception {
+  private java.util.Set<String> fetchExistingFieldNames(String collection) {
     CallToolResult schemaResult =
         mcpClient.callTool(new CallToolRequest("get-schema", 
Map.of("collection", collection)));
     assertNotError(schemaResult);
@@ -796,7 +791,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(22)
-  void searchAllShowsAndAssertCount() throws Exception {
+  void searchAllShowsAndAssertCount() {
     CallToolResult result =
         mcpClient.callTool(
             new CallToolRequest(
@@ -814,7 +809,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(23)
-  void searchShowsWithFacetByPlatform() throws Exception {
+  void searchShowsWithFacetByPlatform() {
     CallToolResult result =
         mcpClient.callTool(
             new CallToolRequest(
@@ -863,7 +858,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(24)
-  void searchShowsWithFilterAndKeyword() throws Exception {
+  void searchShowsWithFilterAndKeyword() {
     // Filter to one platform, then full-text search the description field.
     CallToolResult result =
         mcpClient.callTool(
@@ -892,7 +887,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(25)
-  void searchShowsByMultiValuedGenre() throws Exception {
+  void searchShowsByMultiValuedGenre() {
     // Multi-valued strings field: filter on a single genre value.
     CallToolResult result =
         mcpClient.callTool(
@@ -945,7 +940,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(27)
-  void getShowsCollectionStats() throws Exception {
+  void getShowsCollectionStats() {
     CallToolResult result =
         mcpClient.callTool(
             new CallToolRequest("get-collection-stats", Map.of("collection", 
SHOWS_COLLECTION)));
@@ -1189,7 +1184,7 @@ public abstract class McpClientIntegrationTestBase {
 
   @Test
   @Order(39)
-  void blankOptionalSearchEntriesAreIgnoredThroughMcp() throws Exception {
+  void blankOptionalSearchEntriesAreIgnoredThroughMcp() {
     var result =
         mcpClient.callTool(
             new CallToolRequest(
diff --git 
a/src/test/java/org/apache/solr/mcp/server/McpToolRegistrationTest.java 
b/src/test/java/org/apache/solr/mcp/server/McpToolRegistrationTest.java
index be3da514..1de409fd 100644
--- a/src/test/java/org/apache/solr/mcp/server/McpToolRegistrationTest.java
+++ b/src/test/java/org/apache/solr/mcp/server/McpToolRegistrationTest.java
@@ -213,14 +213,12 @@ class McpToolRegistrationTest {
 
     // Verify first parameter (collection) is required
     McpToolParam firstParam = parameters[0].getAnnotation(McpToolParam.class);
-    assertTrue(
-        firstParam.required() || !firstParam.required(),
-        "First parameter annotation should specify required status");
+    assertTrue(firstParam.required(), "The collection parameter should be 
required");
 
     // Verify optional parameters have required=false
     for (int i = 1; i < parameters.length; i++) {
       McpToolParam param = parameters[i].getAnnotation(McpToolParam.class);
-      // Optional parameters should be marked as such in description or 
required flag
+      assertFalse(param.required(), parameters[i].getName() + " should be 
optional");
       assertNotNull(
           param.description(), "Parameter should have description indicating 
if it's optional");
     }
diff --git 
a/src/test/java/org/apache/solr/mcp/server/SampleDataRuntimeHints.java 
b/src/test/java/org/apache/solr/mcp/server/SampleDataRuntimeHints.java
index faf0e86e..25baf758 100644
--- a/src/test/java/org/apache/solr/mcp/server/SampleDataRuntimeHints.java
+++ b/src/test/java/org/apache/solr/mcp/server/SampleDataRuntimeHints.java
@@ -38,7 +38,7 @@ import 
org.springframework.test.context.aot.TestRuntimeHintsRegistrar;
  * It lives in test sources on purpose: {@code SolrNativeHints} is the 
production equivalent and
  * must not ship test fixture names in the application image.
  *
- * @see org.apache.solr.mcp.server.indexing.ShowsSampleDataIntegrationTest
+ * <p>Used by {@code indexing.ShowsSampleDataIntegrationTest}.
  */
 public class SampleDataRuntimeHints implements TestRuntimeHintsRegistrar {
 
diff --git 
a/src/test/java/org/apache/solr/mcp/server/collection/AliasServiceTest.java 
b/src/test/java/org/apache/solr/mcp/server/collection/AliasServiceTest.java
index 94cdc741..a86e0b19 100644
--- a/src/test/java/org/apache/solr/mcp/server/collection/AliasServiceTest.java
+++ b/src/test/java/org/apache/solr/mcp/server/collection/AliasServiceTest.java
@@ -113,14 +113,6 @@ class AliasServiceTest {
           .hasMessage("Alias name must not be blank");
     }
 
-    @Test
-    @DisplayName("throws IllegalArgumentException when alias name is null")
-    void throwsWhenAliasNameNull() {
-      assertThatThrownBy(() -> aliasService.createAlias(null, "ORDERS_V2"))
-          .isInstanceOf(IllegalArgumentException.class)
-          .hasMessage("Alias name must not be blank");
-    }
-
     @Test
     @DisplayName("throws IllegalArgumentException when collections is blank")
     void throwsWhenCollectionsBlank() {
@@ -128,14 +120,6 @@ class AliasServiceTest {
           .isInstanceOf(IllegalArgumentException.class)
           .hasMessage("Collections must not be blank");
     }
-
-    @Test
-    @DisplayName("throws IllegalArgumentException when collections is null")
-    void throwsWhenCollectionsNull() {
-      assertThatThrownBy(() -> aliasService.createAlias("ORDERS", null))
-          .isInstanceOf(IllegalArgumentException.class)
-          .hasMessage("Collections must not be blank");
-    }
   }
 
   @Nested
@@ -168,13 +152,5 @@ class AliasServiceTest {
           .isInstanceOf(IllegalArgumentException.class)
           .hasMessage("Alias name must not be blank");
     }
-
-    @Test
-    @DisplayName("throws IllegalArgumentException when alias name is null")
-    void throwsWhenAliasNameNull() {
-      assertThatThrownBy(() -> aliasService.deleteAlias(null))
-          .isInstanceOf(IllegalArgumentException.class)
-          .hasMessage("Alias name must not be blank");
-    }
   }
 }
diff --git 
a/src/test/java/org/apache/solr/mcp/server/collection/CollectionServiceIntegrationTest.java
 
b/src/test/java/org/apache/solr/mcp/server/collection/CollectionServiceIntegrationTest.java
index 3cb17f4a..8573b12a 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/collection/CollectionServiceIntegrationTest.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/collection/CollectionServiceIntegrationTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.mcp.server.collection;
 
+import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -138,9 +139,7 @@ class CollectionServiceIntegrationTest {
     assertNotNull(queryStats);
     assertNotNull(queryStats.queryTime());
     assertEquals(
-        (long) DOC_COUNT,
-        queryStats.totalResults(),
-        "totalResults should match indexed document count");
+        DOC_COUNT, queryStats.totalResults(), "totalResults should match 
indexed document count");
     assertEquals(0L, queryStats.start());
 
     if (!MBEANS_AVAILABLE) {
@@ -153,29 +152,29 @@ class CollectionServiceIntegrationTest {
     assertNotNull(metrics.cacheStats(), "Cache stats should not be null after 
queries ran");
     CacheStats cacheStats = metrics.cacheStats();
     assertNotNull(cacheStats.queryResultCache());
-    assertTrue(
-        cacheStats.queryResultCache().lookups() > 0,
-        "Query result cache should have lookups after queries");
+    assertThat(cacheStats.queryResultCache().lookups())
+        .as("Query result cache should have lookups after queries")
+        .isPositive();
     assertNotNull(cacheStats.documentCache());
-    assertTrue(
-        cacheStats.documentCache().lookups() > 0,
-        "Document cache should have lookups after queries");
+    assertThat(cacheStats.documentCache().lookups())
+        .as("Document cache should have lookups after queries")
+        .isPositive();
     assertNotNull(cacheStats.filterCache());
-    assertTrue(
-        cacheStats.filterCache().lookups() > 0,
-        "Filter cache should have lookups after filter queries");
+    assertThat(cacheStats.filterCache().lookups())
+        .as("Filter cache should have lookups after filter queries")
+        .isPositive();
 
     // Handler stats should reflect actual request counts
     assertNotNull(metrics.handlerStats(), "Handler stats should not be null 
after queries ran");
     HandlerStats handlerStats = metrics.handlerStats();
     assertNotNull(handlerStats.selectHandler());
-    assertTrue(
-        handlerStats.selectHandler().requests() > 0,
-        "Select handler should have processed requests");
+    assertThat(handlerStats.selectHandler().requests())
+        .as("Select handler should have processed requests")
+        .isPositive();
     assertNotNull(handlerStats.updateHandler());
-    assertTrue(
-        handlerStats.updateHandler().requests() > 0,
-        "Update handler should have processed requests from indexing");
+    assertThat(handlerStats.updateHandler().requests())
+        .as("Update handler should have processed requests from indexing")
+        .isPositive();
   }
 
   @Test
@@ -206,9 +205,7 @@ class CollectionServiceIntegrationTest {
     assertTrue(status.responseTime() >= 0);
 
     assertEquals(
-        (long) DOC_COUNT,
-        status.totalDocuments(),
-        "Health check should report indexed document count");
+        DOC_COUNT, status.totalDocuments(), "Health check should report 
indexed document count");
 
     assertNotNull(status.lastChecked());
     assertTrue(
@@ -235,12 +232,11 @@ class CollectionServiceIntegrationTest {
     assertEquals(TEST_COLLECTION, 
collectionService.extractCollectionName(TEST_COLLECTION));
     assertEquals("films", 
collectionService.extractCollectionName("films_shard1_replica_n1"));
     assertEquals("products", 
collectionService.extractCollectionName("products_shard2_replica_n3"));
-    assertNull(collectionService.extractCollectionName(null));
     assertEquals("", collectionService.extractCollectionName(""));
   }
 
   @Test
-  void testGetCacheMetrics_afterQueries() throws Exception {
+  void testGetCacheMetrics_afterQueries() {
     CacheStats cacheStats = collectionService.getCacheMetrics(TEST_COLLECTION);
 
     if (!MBEANS_AVAILABLE) {
@@ -253,23 +249,29 @@ class CollectionServiceIntegrationTest {
     // Query result cache: warm-up queries should have generated lookups
     CacheInfo qrc = cacheStats.queryResultCache();
     assertNotNull(qrc);
-    assertTrue(qrc.lookups() > 0, "queryResultCache lookups should be positive 
after queries");
+    assertThat(qrc.lookups())
+        .as("queryResultCache lookups should be positive after queries")
+        .isPositive();
     assertNotNull(qrc.hitratio());
     assertNotNull(qrc.size());
 
     // Document cache: reading documents populates this cache
     CacheInfo dc = cacheStats.documentCache();
     assertNotNull(dc);
-    assertTrue(dc.lookups() > 0, "documentCache lookups should be positive 
after queries");
+    assertThat(dc.lookups())
+        .as("documentCache lookups should be positive after queries")
+        .isPositive();
 
     // Filter cache: the filter queries we ran should generate lookups
     CacheInfo fc = cacheStats.filterCache();
     assertNotNull(fc);
-    assertTrue(fc.lookups() > 0, "filterCache lookups should be positive after 
filter queries");
+    assertThat(fc.lookups())
+        .as("filterCache lookups should be positive after filter queries")
+        .isPositive();
   }
 
   @Test
-  void testGetHandlerMetrics_afterQueriesAndIndexing() throws Exception {
+  void testGetHandlerMetrics_afterQueriesAndIndexing() {
     HandlerStats handlerStats = 
collectionService.getHandlerMetrics(TEST_COLLECTION);
 
     if (!MBEANS_AVAILABLE) {
@@ -282,7 +284,9 @@ class CollectionServiceIntegrationTest {
     // Select handler: warm-up queries should have driven request counts > 0
     HandlerInfo select = handlerStats.selectHandler();
     assertNotNull(select);
-    assertTrue(select.requests() > 0, "Select handler requests should be 
positive after queries");
+    assertThat(select.requests())
+        .as("Select handler requests should be positive after queries")
+        .isPositive();
     // Solr may omit these counters entirely or report an explicit 0 - both 
mean
     // "nothing went wrong". Requiring null made the test depend on which.
     assertTrue(
@@ -294,16 +298,18 @@ class CollectionServiceIntegrationTest {
     // Update handler: indexing 50 docs should have driven request counts > 0
     HandlerInfo update = handlerStats.updateHandler();
     assertNotNull(update);
-    assertTrue(update.requests() > 0, "Update handler requests should be 
positive after indexing");
+    assertThat(update.requests())
+        .as("Update handler requests should be positive after indexing")
+        .isPositive();
   }
 
   @Test
-  void testGetCacheMetrics_nonExistentCollection() throws Exception {
+  void testGetCacheMetrics_nonExistentCollection() {
     assertNull(collectionService.getCacheMetrics("non_existent_collection"));
   }
 
   @Test
-  void testGetHandlerMetrics_nonExistentCollection() throws Exception {
+  void testGetHandlerMetrics_nonExistentCollection() {
     assertNull(collectionService.getHandlerMetrics("non_existent_collection"));
   }
 
diff --git 
a/src/test/java/org/apache/solr/mcp/server/collection/CollectionServiceTest.java
 
b/src/test/java/org/apache/solr/mcp/server/collection/CollectionServiceTest.java
index 1410c304..cbb22a1e 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/collection/CollectionServiceTest.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/collection/CollectionServiceTest.java
@@ -39,6 +39,7 @@ import org.apache.solr.client.solrj.response.SolrPingResponse;
 import org.apache.solr.common.SolrDocumentList;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.NamedList;
+import org.jspecify.annotations.Nullable;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.condition.DisabledInNativeImage;
@@ -118,15 +119,6 @@ class CollectionServiceTest {
     assertEquals("simple_collection", result);
   }
 
-  @Test
-  void extractCollectionName_WithNullInput_ShouldReturnNull() {
-    // When
-    String result = collectionService.extractCollectionName(null);
-
-    // Then
-    assertNull(result);
-  }
-
   @Test
   void extractCollectionName_WithEmptyString_ShouldReturnEmptyString() {
     // When
@@ -297,7 +289,6 @@ class CollectionServiceTest {
     assertNotNull(result);
     assertTrue(result.isHealthy());
     assertEquals(5000, result.responseTime());
-    assertTrue(result.responseTime() > 1000, "Should capture slow response 
time");
   }
 
   @Test
@@ -307,6 +298,7 @@ class CollectionServiceTest {
     SolrHealthStatus result = 
collectionService.checkHealth("error_collection");
 
     assertFalse(result.isHealthy());
+    assertNotNull(result.errorMessage());
     assertTrue(result.errorMessage().contains("Network error"));
     assertNull(result.responseTime());
   }
@@ -706,7 +698,6 @@ class CollectionServiceTest {
 
     HandlerStats emptyStats = new HandlerStats(null, null);
     assertTrue((boolean) method.invoke(collectionService, emptyStats));
-    assertTrue((boolean) method.invoke(collectionService, (HandlerStats) 
null));
 
     HandlerStats nonEmptyStats =
         new HandlerStats(new HandlerInfo(100L, null, null, null, null, null), 
null);
@@ -731,7 +722,7 @@ class CollectionServiceTest {
 
   @Test
   void listCollections_NullCollections() throws Exception {
-    NamedList<Object> response = new NamedList<>();
+    NamedList<@Nullable Object> response = new NamedList<>();
     response.add("collections", null);
 
     when(solrClient.request(any(), any())).thenReturn(response);
@@ -835,10 +826,6 @@ class CollectionServiceTest {
     return wrapInMetricsResponse(createSelectHandlerCoreMetrics(), collection);
   }
 
-  private NamedList<Object> createMockUpdateHandlerData() {
-    return wrapInMetricsResponse(createUpdateHandlerCoreMetrics());
-  }
-
   private NamedList<Object> wrapInMetricsResponse(NamedList<Object> 
coreMetrics) {
     return wrapInMetricsResponse(coreMetrics, "test_collection");
   }
@@ -975,11 +962,11 @@ class CollectionServiceTest {
   }
 
   @Test
+  @SuppressWarnings("DataFlowIssue") // passes null on purpose to pin the SDK 
contract
   void completeCollection_NullValueRejectedAtSdkBoundary() {
     // Since MCP Java SDK 2.0, CompleteArgument's constructor rejects a null
-    // value (1.x and earlier accept it), so the
-    // CollectionService#completeCollection null-value branch is unreachable
-    // from a real MCP client. Document the SDK contract here.
+    // value (1.x and earlier accept it), so 
CollectionService#completeCollection
+    // does not null-check it. Pin the SDK contract here.
     assertThrows(
         IllegalArgumentException.class,
         () -> new CompleteRequest.CompleteArgument("collection", null));
@@ -1019,13 +1006,6 @@ class CollectionServiceTest {
     verify(spyService, never()).listCollections();
   }
 
-  @Test
-  void completeCollection_WithNullArgument_ReturnsEmpty() {
-    List<String> result = collectionService.completeCollection(null);
-
-    assertTrue(result.isEmpty());
-  }
-
   @Test
   void completeCollection_CapsResultsAtMax() throws Exception {
     CollectionService spyService = spy(collectionService);
diff --git 
a/src/test/java/org/apache/solr/mcp/server/collection/CollectionUtilsTest.java 
b/src/test/java/org/apache/solr/mcp/server/collection/CollectionUtilsTest.java
index 5a3c7dd1..a98f5a45 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/collection/CollectionUtilsTest.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/collection/CollectionUtilsTest.java
@@ -16,12 +16,15 @@
  */
 package org.apache.solr.mcp.server.collection;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.within;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import org.apache.solr.common.util.NamedList;
+import org.jspecify.annotations.Nullable;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -32,7 +35,7 @@ class CollectionUtilsTest {
 
   @Test
   void testGetLong_withNullValue() {
-    NamedList<Object> namedList = new NamedList<>();
+    NamedList<@Nullable Object> namedList = new NamedList<>();
     namedList.add("nullKey", null);
 
     assertNull(CollectionUtils.getLong(namedList, "nullKey"));
@@ -119,7 +122,7 @@ class CollectionUtilsTest {
 
   @Test
   void testGetFloat_withNullValue() {
-    NamedList<Object> namedList = new NamedList<>();
+    NamedList<@Nullable Object> namedList = new NamedList<>();
     namedList.add("nullKey", null);
 
     assertNull(CollectionUtils.getFloat(namedList, "nullKey"));
@@ -145,7 +148,7 @@ class CollectionUtilsTest {
     NamedList<Object> namedList = new NamedList<>();
     namedList.add("floatKey", 123.45f);
 
-    assertEquals(123.45f, CollectionUtils.getFloat(namedList, "floatKey"), 
0.001f);
+    assertThat(CollectionUtils.getFloat(namedList, 
"floatKey")).isCloseTo(123.45f, within(0.001f));
   }
 
   @Test
@@ -153,7 +156,8 @@ class CollectionUtilsTest {
     NamedList<Object> namedList = new NamedList<>();
     namedList.add("doubleKey", 123.456789);
 
-    assertEquals(123.456789f, CollectionUtils.getFloat(namedList, 
"doubleKey"), 0.001f);
+    assertThat(CollectionUtils.getFloat(namedList, "doubleKey"))
+        .isCloseTo(123.456789f, within(0.001f));
   }
 
   @Test
@@ -169,7 +173,7 @@ class CollectionUtilsTest {
     NamedList<Object> namedList = new NamedList<>();
     namedList.add("bigDecKey", new BigDecimal("123.45"));
 
-    assertEquals(123.45f, CollectionUtils.getFloat(namedList, "bigDecKey"), 
0.001f);
+    assertThat(CollectionUtils.getFloat(namedList, 
"bigDecKey")).isCloseTo(123.45f, within(0.001f));
   }
 
   @Test
@@ -177,7 +181,7 @@ class CollectionUtilsTest {
     NamedList<Object> namedList = new NamedList<>();
     namedList.add("stringKey", "123.45");
 
-    assertEquals(123.45f, CollectionUtils.getFloat(namedList, "stringKey"), 
0.001f);
+    assertThat(CollectionUtils.getFloat(namedList, 
"stringKey")).isCloseTo(123.45f, within(0.001f));
   }
 
   @Test
@@ -198,7 +202,7 @@ class CollectionUtilsTest {
 
   @Test
   void testGetInteger_withNullValue() {
-    NamedList<Object> namedList = new NamedList<>();
+    NamedList<@Nullable Object> namedList = new NamedList<>();
     namedList.add("nullKey", null);
 
     assertNull(CollectionUtils.getInteger(namedList, "nullKey"));
@@ -298,7 +302,8 @@ class CollectionUtilsTest {
     NamedList<Object> namedList = new NamedList<>();
     namedList.add("negativeKey", -123.45f);
 
-    assertEquals(-123.45f, CollectionUtils.getFloat(namedList, "negativeKey"), 
0.001f);
+    assertThat(CollectionUtils.getFloat(namedList, "negativeKey"))
+        .isCloseTo(-123.45f, within(0.001f));
   }
 
   @Test
diff --git 
a/src/test/java/org/apache/solr/mcp/server/config/SolrConfigurationPropertiesTest.java
 
b/src/test/java/org/apache/solr/mcp/server/config/SolrConfigurationPropertiesTest.java
index 9741442c..6565c040 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/config/SolrConfigurationPropertiesTest.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/config/SolrConfigurationPropertiesTest.java
@@ -74,6 +74,7 @@ class SolrConfigurationPropertiesTest {
 
   /** The binder passes null when the property is absent altogether. */
   @Test
+  @SuppressWarnings("DataFlowIssue") // passes null on purpose, as the binder 
does
   void rejectsAMissingUrl() {
     assertThatThrownBy(() -> new SolrConfigurationProperties(null, null, null))
         .isInstanceOf(IllegalArgumentException.class)
diff --git 
a/src/test/java/org/apache/solr/mcp/server/containerization/DockerImageHttpIntegrationTest.java
 
b/src/test/java/org/apache/solr/mcp/server/containerization/DockerImageHttpIntegrationTest.java
index 43850bae..883fb849 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/containerization/DockerImageHttpIntegrationTest.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/containerization/DockerImageHttpIntegrationTest.java
@@ -131,22 +131,19 @@ class DockerImageHttpIntegrationTest {
                   .forPort(HTTP_PORT)
                   .withStartupTimeout(Duration.ofSeconds(60)));
 
-  private static HttpClient httpClient;
-  private static String baseUrl;
+  private static final HttpClient httpClient =
+      HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
 
   @BeforeAll
   static void setup() {
     log.info("Solr container started. Internal URL: http://solr:8983/solr/";);
     log.info("MCP Server container started in HTTP mode");
+    log.info("MCP Server HTTP endpoint available at: {}", baseUrl());
+  }
 
-    // Initialize HTTP client
-    httpClient = 
HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();
-
-    // Get the mapped port for accessing the container from the host
-    Integer mappedPort = mcpServerContainer.getMappedPort(HTTP_PORT);
-    baseUrl = "http://localhost:"; + mappedPort;
-
-    log.info("MCP Server HTTP endpoint available at: {}", baseUrl);
+  /** The MCP server's HTTP endpoint, through the port the container is mapped 
to on the host. */
+  private static String baseUrl() {
+    return "http://localhost:"; + mcpServerContainer.getMappedPort(HTTP_PORT);
   }
 
   @Test
@@ -202,7 +199,7 @@ class DockerImageHttpIntegrationTest {
     // Test that the HTTP endpoint is accessible
     HttpRequest request =
         HttpRequest.newBuilder()
-            .uri(URI.create(baseUrl + "/actuator/health"))
+            .uri(URI.create(baseUrl() + "/actuator/health"))
             .timeout(Duration.ofSeconds(10))
             .GET()
             .build();
diff --git 
a/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceIntegrationTest.java
 
b/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceIntegrationTest.java
index 8ba64a1e..c7ea1ce4 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceIntegrationTest.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceIntegrationTest.java
@@ -40,7 +40,6 @@ import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.context.annotation.Import;
-import org.testcontainers.containers.SolrContainer;
 import org.testcontainers.junit.jupiter.Testcontainers;
 
 /** Integration tests for IndexingService using a real Solr instance via 
Testcontainers. */
@@ -52,7 +51,6 @@ class IndexingServiceIntegrationTest {
 
   private static final String COLLECTION_NAME = "indexing_test_" + 
System.currentTimeMillis();
   private static boolean initialized = false;
-  @Autowired private SolrContainer solrContainer;
   @Autowired private IndexingDocumentCreator indexingDocumentCreator;
   @Autowired private IndexingService indexingService;
   @Autowired private SearchService searchService;
@@ -71,7 +69,7 @@ class IndexingServiceIntegrationTest {
   }
 
   @Test
-  void testCreateSchemalessDocumentsFromJson() throws Exception {
+  void testCreateSchemalessDocumentsFromJson() {
     // Test JSON string
     String json =
         """
@@ -124,9 +122,6 @@ class IndexingServiceIntegrationTest {
       } else {
         assertEquals(true, inStockValue);
       }
-    } else {
-      // If inStock is not present in the document, we'll skip this assertion
-      // Removed debug print statement
     }
 
     Object authorValue = doc.getFieldValue("author");
@@ -642,7 +637,7 @@ class IndexingServiceIntegrationTest {
   }
 
   @Test
-  void testNonArrayJsonInput() throws Exception {
+  void testNonArrayJsonInput() {
     // Test JSON string that is not an array but a single object
     String json =
         """
@@ -667,7 +662,7 @@ class IndexingServiceIntegrationTest {
   }
 
   @Test
-  void testConvertJsonValueTypes() throws Exception {
+  void testConvertJsonValueTypes() {
     // Test JSON with different value types
     String json =
         """
@@ -703,7 +698,7 @@ class IndexingServiceIntegrationTest {
   }
 
   @Test
-  void testDirectFieldNamesAreUsedAsGiven() throws Exception {
+  void testDirectFieldNamesAreUsedAsGiven() {
     String json =
         """
                                [
diff --git 
a/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceTest.java 
b/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceTest.java
index 983ead72..0e31b95c 100644
--- a/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceTest.java
+++ b/src/test/java/org/apache/solr/mcp/server/indexing/IndexingServiceTest.java
@@ -387,15 +387,6 @@ class IndexingServiceTest {
     assertEquals(2, captor.getValue().getDocuments().size());
   }
 
-  @Test
-  void 
indexMarkdownDocuments_WithNullDocuments_ThrowsIllegalArgumentException() {
-    IllegalArgumentException ex =
-        assertThrows(
-            IllegalArgumentException.class,
-            () -> indexingService.indexMarkdownDocuments("test_collection", 
null));
-    assertEquals("documents cannot be null", ex.getMessage());
-  }
-
   @Test
   void indexDataPrompt_jsonPath_referencesIndexJsonDocuments() {
     String sample =
diff --git 
a/src/test/java/org/apache/solr/mcp/server/indexing/MarkdownIndexingTest.java 
b/src/test/java/org/apache/solr/mcp/server/indexing/MarkdownIndexingTest.java
index 12035dd9..eac84750 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/indexing/MarkdownIndexingTest.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/indexing/MarkdownIndexingTest.java
@@ -42,7 +42,7 @@ class MarkdownIndexingTest {
   @Autowired private IndexingDocumentCreator indexingDocumentCreator;
 
   @Test
-  void testCreateSchemalessDocumentsFromMarkdownWithFrontMatter() throws 
Exception {
+  void testCreateSchemalessDocumentsFromMarkdownWithFrontMatter() {
     // Given
     String markdown =
         """
@@ -89,7 +89,7 @@ class MarkdownIndexingTest {
   }
 
   @Test
-  void testFrontMatterTitleWinsOverFirstHeading() throws Exception {
+  void testFrontMatterTitleWinsOverFirstHeading() {
     // Given
     String markdown =
         """
@@ -111,7 +111,7 @@ class MarkdownIndexingTest {
   }
 
   @Test
-  void testCreateSchemalessDocumentsFromMarkdownWithoutFrontMatter() throws 
Exception {
+  void testCreateSchemalessDocumentsFromMarkdownWithoutFrontMatter() {
     // Given
     String markdown =
         """
@@ -141,7 +141,7 @@ class MarkdownIndexingTest {
   }
 
   @Test
-  void testPlainTextMarkdownWithoutHeadings() throws Exception {
+  void testPlainTextMarkdownWithoutHeadings() {
     // Given
     String markdown = "Just a plain paragraph of text without any structure.";
 
@@ -160,7 +160,7 @@ class MarkdownIndexingTest {
   }
 
   @Test
-  void testFrontMatterFieldNamesAreUsedAsGiven() throws Exception {
+  void testFrontMatterFieldNamesAreUsedAsGiven() {
     // Given
     String markdown =
         """
@@ -186,7 +186,7 @@ class MarkdownIndexingTest {
   }
 
   @Test
-  void testFrontMatterListValuesBecomeMultiValuedFields() throws Exception {
+  void testFrontMatterListValuesBecomeMultiValuedFields() {
     // Given
     String markdown =
         """
@@ -209,7 +209,7 @@ class MarkdownIndexingTest {
   }
 
   @Test
-  void testFrontMatterBlockListValuesBecomeMultiValuedFields() throws 
Exception {
+  void testFrontMatterBlockListValuesBecomeMultiValuedFields() {
     // Given
     String markdown =
         """
@@ -233,7 +233,7 @@ class MarkdownIndexingTest {
   }
 
   @Test
-  void testFrontMatterIdIsUsedAsDocumentId() throws Exception {
+  void testFrontMatterIdIsUsedAsDocumentId() {
     // Given
     String markdown =
         """
@@ -255,7 +255,7 @@ class MarkdownIndexingTest {
   }
 
   @Test
-  void testGeneratedIdIsStableForSameContent() throws Exception {
+  void testGeneratedIdIsStableForSameContent() {
     // Given
     String markdown = "# Stable\n\nSame content, same id.";
 
@@ -279,11 +279,11 @@ class MarkdownIndexingTest {
   void testEmptyMarkdownThrowsException() {
     assertThatThrownBy(() -> 
indexingDocumentCreator.createSchemalessDocumentsFromMarkdown(""))
         .isInstanceOf(DocumentProcessingException.class)
-        .hasMessageContaining("cannot be null or empty");
+        .hasMessageContaining("cannot be empty");
   }
 
   @Test
-  void testMarkdownFormattingIsStrippedFromContent() throws Exception {
+  void testMarkdownFormattingIsStrippedFromContent() {
     // Given
     String markdown =
         """
diff --git 
a/src/test/java/org/apache/solr/mcp/server/indexing/documentcreator/JsonDocumentCreatorTest.java
 
b/src/test/java/org/apache/solr/mcp/server/indexing/documentcreator/JsonDocumentCreatorTest.java
index 2937f4c9..bcd3024f 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/indexing/documentcreator/JsonDocumentCreatorTest.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/indexing/documentcreator/JsonDocumentCreatorTest.java
@@ -42,7 +42,7 @@ class JsonDocumentCreatorTest {
   private final JsonDocumentCreator creator = new 
JsonDocumentCreator(objectMapper);
 
   @Test
-  void objectsAndStringProduceTheSameDocuments() throws Exception {
+  void objectsAndStringProduceTheSameDocuments() {
     List<Map<String, Object>> objects =
         List.of(
             show("netflix-001", "Stranger Things", 8.7, true, "Sci-Fi", 
"Horror"),
@@ -79,18 +79,6 @@ class JsonDocumentCreatorTest {
         .hasMessage("JSON input cannot be empty");
   }
 
-  /**
-   * A missing {@code documents} argument arrives as null. The creator owns 
the "nothing to index"
-   * policy for both entry points, so it reports null the same way it reports 
empty rather than
-   * letting the service NPE.
-   */
-  @Test
-  void nullListIsRejected() {
-    assertThatThrownBy(() -> creator.create((List<Map<String, Object>>) null))
-        .isInstanceOf(DocumentProcessingException.class)
-        .hasMessage("JSON input cannot be empty");
-  }
-
   private static Map<String, Object> show(
       String id, String title, double rating, boolean ongoing, String... 
genres) {
     Map<String, Object> show = new LinkedHashMap<>();
diff --git 
a/src/test/java/org/apache/solr/mcp/server/observability/DistributedTracingTest.java
 
b/src/test/java/org/apache/solr/mcp/server/observability/DistributedTracingTest.java
index 13b3929e..4e887e9d 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/observability/DistributedTracingTest.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/observability/DistributedTracingTest.java
@@ -20,8 +20,8 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
 
 import io.micrometer.tracing.test.simple.SimpleTracer;
+import java.time.Instant;
 import java.util.concurrent.TimeUnit;
-import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.mcp.server.TestcontainersConfiguration;
 import org.apache.solr.mcp.server.search.SearchService;
 import org.junit.jupiter.api.AfterEach;
@@ -72,8 +72,6 @@ class DistributedTracingTest {
 
   @Autowired private SearchService searchService;
 
-  @Autowired private SolrClient solrClient;
-
   @Autowired private SimpleTracer tracer;
 
   @BeforeEach
@@ -218,7 +216,9 @@ class DistributedTracingTest {
               assertThat(spans)
                   .as("All spans should have start and end times")
                   .allMatch(
-                      span -> span.getStartTimestamp() != null && 
span.getEndTimestamp() != null);
+                      span ->
+                          span.getStartTimestamp().isAfter(Instant.EPOCH)
+                              && 
!span.getEndTimestamp().isBefore(span.getStartTimestamp()));
             });
   }
 }
diff --git 
a/src/test/java/org/apache/solr/mcp/server/observability/TraceAssertions.java 
b/src/test/java/org/apache/solr/mcp/server/observability/TraceAssertions.java
index ea77b906..fd93118f 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/observability/TraceAssertions.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/observability/TraceAssertions.java
@@ -23,6 +23,7 @@ import io.opentelemetry.api.trace.SpanKind;
 import io.opentelemetry.sdk.trace.data.SpanData;
 import java.util.List;
 import java.util.function.Predicate;
+import org.jspecify.annotations.Nullable;
 
 /**
  * Helper utilities for asserting on distributed traces in tests.
@@ -165,7 +166,7 @@ public class TraceAssertions {
    * @param spanName the span name to search for
    * @return the first matching span, or null if not found
    */
-  public static SpanData findSpan(List<SpanData> spans, String spanName) {
+  public static @Nullable SpanData findSpan(List<SpanData> spans, String 
spanName) {
     return spans.stream()
         .filter(span -> span.getName().contains(spanName))
         .findFirst()
diff --git 
a/src/test/java/org/apache/solr/mcp/server/schema/SchemaServiceIntegrationTest.java
 
b/src/test/java/org/apache/solr/mcp/server/schema/SchemaServiceIntegrationTest.java
index d0958a50..387a647c 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/schema/SchemaServiceIntegrationTest.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/schema/SchemaServiceIntegrationTest.java
@@ -101,17 +101,6 @@ class SchemaServiceIntegrationTest {
         "Getting schema for non-existent collection should throw exception");
   }
 
-  @Test
-  void testGetSchema_NullCollection() {
-    // When/Then
-    assertThrows(
-        Exception.class,
-        () -> {
-          schemaService.getSchema(null);
-        },
-        "Getting schema with null collection should throw exception");
-  }
-
   @Test
   void testGetSchema_EmptyCollection() {
     // When/Then
@@ -160,8 +149,8 @@ class SchemaServiceIntegrationTest {
     // Then - verify dynamic fields are accessible
     assertNotNull(schema.getDynamicFields(), "Dynamic fields should not be 
null");
 
-    // Most Solr schemas have some dynamic fields by default
-    assertTrue(schema.getDynamicFields().size() >= 0, "Dynamic fields should 
be a valid list");
+    // The _default configset defines dynamic fields such as *_s and *_i
+    assertFalse(schema.getDynamicFields().isEmpty(), "Dynamic fields should 
not be empty");
 
     // Check for common dynamic field patterns
     boolean hasStringDynamicField =
@@ -182,9 +171,6 @@ class SchemaServiceIntegrationTest {
 
     // Then - verify copy fields are accessible
     assertNotNull(schema.getCopyFields(), "Copy fields should not be null");
-
-    // Copy fields list can be empty, that's valid
-    assertTrue(schema.getCopyFields().size() >= 0, "Copy fields should be a 
valid list");
   }
 
   @Test
diff --git 
a/src/test/java/org/apache/solr/mcp/server/schema/SchemaServiceTest.java 
b/src/test/java/org/apache/solr/mcp/server/schema/SchemaServiceTest.java
index 8156e95f..d36670ef 100644
--- a/src/test/java/org/apache/solr/mcp/server/schema/SchemaServiceTest.java
+++ b/src/test/java/org/apache/solr/mcp/server/schema/SchemaServiceTest.java
@@ -30,8 +30,6 @@ import org.apache.solr.client.solrj.SolrRequest;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.request.schema.SchemaRequest;
 import org.apache.solr.client.solrj.request.schema.SchemaRequest.MultiUpdate;
-import org.apache.solr.client.solrj.response.schema.SchemaRepresentation;
-import org.apache.solr.client.solrj.response.schema.SchemaResponse;
 import org.apache.solr.common.util.NamedList;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -54,10 +52,6 @@ class SchemaServiceTest {
 
   @Mock private ObjectMapper objectMapper;
 
-  @Mock private SchemaResponse schemaResponse;
-
-  @Mock private SchemaRepresentation schemaRepresentation;
-
   private SchemaService schemaService;
 
   @BeforeEach
@@ -125,18 +119,6 @@ class SchemaServiceTest {
         });
   }
 
-  @Test
-  void testGetSchema_WithNullCollection() {
-    // Given a null collection name
-    // Then should throw an exception (NullPointerException or
-    // IllegalArgumentException)
-    assertThrows(
-        Exception.class,
-        () -> {
-          schemaService.getSchema(null);
-        });
-  }
-
   @Test
   void testGetSchema_WithEmptyCollection() {
     // Given an empty collection name
@@ -155,20 +137,8 @@ class SchemaServiceTest {
     assertNotNull(service);
   }
 
-  @Test
-  void testConstructor_WithNullClient() {
-    // Test constructor with null client
-    assertDoesNotThrow(
-        () -> {
-          new SchemaService(null, objectMapper);
-        });
-  }
-
   @Test
   void addFields_blankCollection_throws() {
-    assertThrows(
-        IllegalArgumentException.class,
-        () -> schemaService.addFields(null, List.of(Map.of("name", "x", 
"type", "string"))));
     assertThrows(
         IllegalArgumentException.class,
         () -> schemaService.addFields("", List.of(Map.of("name", "x", "type", 
"string"))));
@@ -179,7 +149,6 @@ class SchemaServiceTest {
 
   @Test
   void addFields_emptyList_throws() {
-    assertThrows(IllegalArgumentException.class, () -> 
schemaService.addFields("col", null));
     assertThrows(IllegalArgumentException.class, () -> 
schemaService.addFields("col", List.of()));
   }
 
@@ -224,11 +193,6 @@ class SchemaServiceTest {
 
   @Test
   void addFieldTypes_blankCollection_throws() {
-    assertThrows(
-        IllegalArgumentException.class,
-        () ->
-            schemaService.addFieldTypes(
-                null, List.of(Map.of("name", "x", "class", "solr.StrField"))));
     assertThrows(
         IllegalArgumentException.class,
         () ->
@@ -243,7 +207,6 @@ class SchemaServiceTest {
 
   @Test
   void addFieldTypes_emptyList_throws() {
-    assertThrows(IllegalArgumentException.class, () -> 
schemaService.addFieldTypes("col", null));
     assertThrows(
         IllegalArgumentException.class, () -> 
schemaService.addFieldTypes("col", List.of()));
   }
diff --git 
a/src/test/java/org/apache/solr/mcp/server/search/SearchServiceIntegrationTest.java
 
b/src/test/java/org/apache/solr/mcp/server/search/SearchServiceIntegrationTest.java
index c462e790..1adb6de3 100644
--- 
a/src/test/java/org/apache/solr/mcp/server/search/SearchServiceIntegrationTest.java
+++ 
b/src/test/java/org/apache/solr/mcp/server/search/SearchServiceIntegrationTest.java
@@ -194,10 +194,9 @@ class SearchServiceIntegrationTest {
    * to one, so a plain list surfaces as {@code ClassCastException: ArrayList 
cannot be cast to
    * NamedList} instead of an empty result.
    *
-   * <p>{@link org.apache.solr.mcp.server.config.JsonResponseParserTest} pins 
the same behaviour at
-   * the parser boundary against a hand-written payload. This test is the 
end-to-end counterpart: it
-   * proves a real Solr actually emits {@code []} for a zero-hit facet, which 
is the premise the
-   * unit tests assume.
+   * <p>{@code config.JsonResponseParserTest} pins the same behaviour at the 
parser boundary against
+   * a hand-written payload. This test is the end-to-end counterpart: it 
proves a real Solr actually
+   * emits {@code []} for a zero-hit facet, which is the premise the unit 
tests assume.
    */
   @Test
   void facetingAQueryThatMatchesNothingReturnsEmptyFacets()
diff --git 
a/src/test/java/org/apache/solr/mcp/server/search/SearchServiceTest.java 
b/src/test/java/org/apache/solr/mcp/server/search/SearchServiceTest.java
index 23f9ed83..53124cf0 100644
--- a/src/test/java/org/apache/solr/mcp/server/search/SearchServiceTest.java
+++ b/src/test/java/org/apache/solr/mcp/server/search/SearchServiceTest.java
@@ -30,7 +30,6 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.List;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.SolrServerException;
@@ -71,13 +70,7 @@ class SearchServiceTest {
     SearchService localService = new SearchService(mockClient);
     assertNotNull(
         localService.search(
-            "test_collection",
-            " \t",
-            Arrays.asList("", " \t", null),
-            Arrays.asList(null, "", " \n"),
-            null,
-            null,
-            null));
+            "test_collection", " \t", List.of("", " \t"), List.of("", " \n"), 
null, null, null));
   }
 
   @Test
@@ -99,7 +92,7 @@ class SearchServiceTest {
             null,
             null,
             null,
-            Arrays.asList(new SortClause("", ""), new SortClause(" \t", 
"asc"), null),
+            List.of(new SortClause("", ""), new SortClause(" \t", "asc")),
             null,
             null));
   }
@@ -129,10 +122,9 @@ class SearchServiceTest {
         localService.search(
             "test_collection",
             "{!edismax qf='name author_ss'}george martin",
-            Arrays.asList("", filter, null, " \n", "price:[0 TO 10]"),
-            Arrays.asList("\t", facet, "", null, "author_ss"),
-            Arrays.asList(
-                null,
+            List.of("", filter, " \n", "price:[0 TO 10]"),
+            List.of("\t", facet, "", "author_ss"),
+            List.of(
                 new SortClause("", "asc"),
                 new SortClause("price", "DESC"),
                 new SortClause(" \n", ""),
@@ -414,8 +406,7 @@ class SearchServiceTest {
     SolrDocumentList mockDocuments = createMockDocumentList();
     when(mockResponse.getResults()).thenReturn(mockDocuments);
     when(mockResponse.getFacetFields()).thenReturn(createMockFacetFields());
-    when(mockClient.query(eq("test_collection"), any(SolrQuery.class)))
-        .thenAnswer(invocation -> mockResponse);
+    when(mockClient.query(eq("test_collection"), 
any(SolrQuery.class))).thenReturn(mockResponse);
     SearchService localService = new SearchService(mockClient);
     SearchResponse result =
         localService.search("test_collection", null, null, facetFields, null, 
null, null);
@@ -432,8 +423,7 @@ class SearchServiceTest {
     SolrDocumentList mockDocuments = createMockDocumentList();
     when(mockResponse.getResults()).thenReturn(mockDocuments);
     when(mockResponse.getFacetFields()).thenReturn(null);
-    when(mockClient.query(eq("test_collection"), any(SolrQuery.class)))
-        .thenAnswer(invocation -> mockResponse);
+    when(mockClient.query(eq("test_collection"), 
any(SolrQuery.class))).thenReturn(mockResponse);
     SearchService localService = new SearchService(mockClient);
     SearchResponse result =
         localService.search("test_collection", null, null, null, sortClauses, 
null, null);
diff --git 
a/src/test/java/org/apache/solr/mcp/server/search/SortClauseTest.java 
b/src/test/java/org/apache/solr/mcp/server/search/SortClauseTest.java
index 76c8a745..f3a64a5b 100644
--- a/src/test/java/org/apache/solr/mcp/server/search/SortClauseTest.java
+++ b/src/test/java/org/apache/solr/mcp/server/search/SortClauseTest.java
@@ -57,8 +57,6 @@ class SortClauseTest {
 
   @Test
   void rejectsMissingField() {
-    assertThrows(
-        IllegalArgumentException.class, () -> new SortClause(null, 
"asc").toSolrSortClause());
     assertThrows(
         IllegalArgumentException.class, () -> new SortClause(" ", 
"asc").toSolrSortClause());
   }

Reply via email to