Skip to content

SOLR-18457: Migrate V2 /update endpoints to using JAX-RS - #4177

Open
epugh wants to merge 57 commits into
apache:mainfrom
epugh:copilot/migrate-update-api-to-jax-rs
Open

epugh wants to merge 57 commits into
apache:mainfrom
epugh:copilot/migrate-update-api-to-jax-rs

Conversation

@epugh

@epugh epugh commented Mar 2, 2026 •

Copy link
Copy Markdown
Contributor

https://issues.apache.org/jira/browse/SOLR-18457

Summary

Migrate the V2 update APIs to JAX-RS while preserving the legacy update handler's indexing behavior. The new API contract exposes the supported update formats, query parameters, request bodies, and typed versioned responses.

API changes

  • Add the JAX-RS UpdateApi contract and Jersey UpdateAPI implementation.
  • Support the generic V2 update endpoint with content-type driven format detection for JSON, XML, CSV, Javabin, and CBOR.
  • Add explicit V2 format paths:
    • /api/collections/{collection}/update/json for document-only JSON indexing
    • /api/collections/{collection}/update/xml for XML updates
    • /api/collections/{collection}/update/csv for CSV updates
    • /api/collections/{collection}/update/javabin for Javabin updates
  • Declare the supported query parameters in the OpenAPI contract, including commit, commitWithin, overwrite, softCommit, and versions.
  • Define the request bodies as streamed update content selected by the Content-Type header.
  • Return a strongly typed V2 response with typed adds, deletes, and deleteByQuery version entries.
  • Preserve response decoration and replication metadata while avoiding duplicate response headers.
  • Preserve the /c/{collection} alias when routing requests through JAX-RS.

The implementation delegates update processing to the existing UpdateRequestHandler, so V1 and V2 continue to share the same loaders, update processors, routing, and commit behavior. The generic /update endpoint accepts JSON update commands; the /update/json path is the document-only JSON convenience endpoint.

Documentation and Admin UI

  • Consolidate V1 and V2 update documentation in indexing-with-update-handlers.adoc, using V1/V2 examples for JSON, XML, CSV, Javabin, versioned responses, and common update parameters.
  • Update partial-document updates and custom JSON transformation examples for both APIs.
  • Document the typed V2 version response and the versions=true behavior.
  • Update the classic Admin UI Documents screen to submit JSON through the V2 update API and simplify its raw command flow.
  • Update the sample techproducts schema for nested documents and in-place updates.

Testing

  • Replace the old mapping test with UpdateAPITest, an integration test using SolrJettyTestRule.
  • Verify V1/V2 parity across supported update formats.
  • Verify custom JSON transformation and indexing through V2.
  • Verify assigned versions, typed versioned responses, XML response headers, and the generic update endpoint.
  • Run the reference guide build and the repository check excluding tests.

Copilot AI and others added 14 commits March 1, 2026 13:45
- Add UpdateApi JAX-RS interface in solr/api module with @path, @post,
  @operation annotations for /update, /update/json, /update/xml,
  /update/csv, /update/bin endpoints
- Rewrite UpdateAPI as a JerseyResource implementing UpdateApi;
  delegates to UpdateRequestHandler; adds UpdateRequestHandlerConfig
  inner class (APIConfigProvider.APIConfig) for injection
- Update V2UpdateRequestHandler to implement APIConfigProvider and
  use getJerseyResources() instead of getApis()
- Replace mock-based V2UpdateAPIMappingTest with UpdateAPITest
  integration test using SolrJettyTestRule

Co-authored-by: epugh <22395+epugh@users.noreply.github.com>
Resolves failing test: NodeHealthAPITest2.testLegacyMode_WithoutMaxGenerationLagReturnsOk

The test verifies that the V2 GET /api/node/health endpoint returns
status=OK in legacy (standalone, non-ZooKeeper) mode when maxGenerationLag
is not specified in the request. This exercises the null-maxGenerationLag
code path in HealthCheckHandler.healthCheckLegacyMode() which immediately
returns OK without checking replication lag.

Test uses SolrJettyTestRule with no ZooKeeper to run in legacy mode.

Co-authored-by: epugh <22395+epugh@users.noreply.github.com>
…n V2Request update/json

The V2Request("/c/{collection}/update/json") call was broken after the UpdateAPI
migration to JAX-RS: the JAX-RS path regex "cores|collections" doesn't match
the "/c/" alias. Replace with the proper SolrJ cloudClient.add() (which goes
through the v1 /update path) and update span name/collection assertions to match.

Co-authored-by: epugh <22395+epugh@users.noreply.github.com>
Normalize /c/ → /collections/ in ContainerRequestUtils.getRequestUri() before
Jersey sees the URI, fixing JAX-RS path matching for all endpoints that use
INDEX_PATH_PREFIX (update, select, schema, etc.) with the /c/ shorthand.

The tracing span name correctly preserves /c/{collection}/... since it is
computed from V2HttpCall.pathSegments which use the original request path.

Co-authored-by: epugh <22395+epugh@users.noreply.github.com>
…o UpdateRequestHandler

DistributedZkUpdateProcessor.handleReplicationFactor() calls
rsp.getResponseHeader().add(...), which requires the response header to be
initialized by SolrCore.preDecorateResponse(). In the JAX-RS path, this
was never called (only the old Api path calls it in executeCoreRequest).

Add pre/postDecorateResponse calls around the updateRequestHandler.handleRequest()
call in UpdateAPI.handleUpdate(), mirroring what SolrCore.execute() does for v1.

Co-authored-by: epugh <22395+epugh@users.noreply.github.com>
Co-authored-by: epugh <22395+epugh@users.noreply.github.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests cat:api labels Mar 2, 2026
@epugh
epugh requested a review from Copilot March 2, 2026 18:15
@epugh epugh changed the title docs: Add indexing-with-v2-apis.adoc for V2 /update API endpoints Migrate V2 /update endpoints to using JAX-RS Mar 2, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Ref Guide documentation for Solr’s V2 update endpoints and (in the same change set) migrates the V2 /update implementation to a JAX-RS resource, including URI normalization to support the /c/ collection shorthand in Jersey routing.

Changes:

  • Added a new Ref Guide page documenting V2 update endpoints and their behavior vs V1 update handlers.
  • Updated indexing guide navigation to include and cross-reference the new V2 update documentation.
  • Switched V2 update handling to a Jersey resource + added /c/ → /collections/ URI normalization, along with updated tests and a new OpenAPI endpoint interface.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc New V2 update API documentation and V1↔V2 comparison.
solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc Cross-reference note + page-children update for new V2 page.
solr/solr-ref-guide/modules/indexing-guide/indexing-nav.adoc Adds the new V2 page to the indexing sidebar nav.
solr/core/src/java/org/apache/solr/jersey/container/ContainerRequestUtils.java Normalizes /c/ collection shorthand for Jersey path matching.
solr/core/src/java/org/apache/solr/handler/admin/api/UpdateAPI.java New Jersey-based V2 update resource delegating to UpdateRequestHandler.
solr/core/src/java/org/apache/solr/handler/V2UpdateRequestHandler.java Registers UpdateAPI as a Jersey resource and provides config injection.
solr/api/src/java/org/apache/solr/client/api/endpoint/UpdateApi.java New OpenAPI/JAX-RS endpoint interface for v2 update paths.
solr/core/src/test/org/apache/solr/handler/V2UpdateAPIMappingTest.java Removes old unit test for v2→v1 update path rewriting.
solr/core/src/test/org/apache/solr/handler.admin.api/UpdateAPITest.java Adds integration tests covering V2 update endpoints.
Comments suppressed due to low confidence (1)

solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-v2-apis.adoc:136

  • Capitalization of “Javabin” here (and in the table / comparison section) is inconsistent with the rest of the ref guide, which uses “JavaBin”. Please standardize on “JavaBin” (while keeping the MIME type application/javabin as-is).
== Javabin Document Indexing

The v2 `/update/bin` endpoint accepts documents in Javabin format, which is the native binary format used by SolrJ.
This endpoint is primarily intended for use by SolrJ clients.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

epugh and others added 2 commits March 3, 2026 08:18
…-v2-apis.adoc

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

@epugh epugh left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guy is tough!

@janhoy

janhoy commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

I hit an issue trying to do atomic update (JSON format) using v2 /update api. Does not work, and SOLR-11265 has been open for 9 years. Then I found this PR which looks promising in making some decent decisions for how the V2 JAX-RS update APIs should work.

I'm leaning towards making the base /api/collections/{collection}/update endpoint support multiple input formats (much like for v1). The HTTP standard supports this through Content-Type already. That way clients can be explicit in what they send and solr never need to doubt. We can also make extension content-types for solr e.g. for the "commands" JSON syntax:

Content-Type Behavior
application/json, text/csv, application/xml Straightforward, same as today's sub-paths
application/javabin or application/vnd.solr.javabin SolrJ's native binary format, already used internally, just exposed as a Content-Type on the main endpoint instead of only /update/bin
application/x-ndjson One JSON doc per line, for streaming bulk loads without a top-level array
application/vnd.solr.commands+json Top-level JSON parsed as the v1-style command envelope (add/delete/commit), restoring what this PR's table marks as unsupported in v2
application/vnd.solr.atomic+json Tells Solr that {"set": ...}, {"add": ...}, {"inc": ...} are atomic-update operators, not literal field structure — directly fixes SOLR-11265 by making intent explicit instead of guessing

Example:

curl -X POST 'http://localhost:8983/api/collections/mycollection/update?commit=true' \
  -H 'Content-Type: application/vnd.solr.atomic+json' \
  --data-binary '{"id": "mydoc", "price": {"set": 99}}'

Not entirely sure whether atomic-syntax should be supported in normal application/json, I think it is problematic since we also need to support nested child docs and that gets ambigous. So an explicit application/vnd.solr.atomic+json would let Solr know what to expect. But then if you want atomic update of a deeply nested JSON -- well, what to do? Time to namespace the atomic qualifiers to avoid confusion, e.g. inc becomes _inc_, set becomes _set_ or similar.

@epugh

epugh commented Sep 12, 2026

Copy link
Copy Markdown
Contributor Author

Not entirely sure whether atomic-syntax should be supported in normal application/json, I think it is problematic since we also need to support nested child docs and that gets ambigous. So an explicit application/vnd.solr.atomic+json would let Solr know what to expect. But then if you want atomic update of a deeply nested JSON -- well, what to do? Time to namespace the atomic qualifiers to avoid confusion, e.g. inc becomes _inc_, set becomes _set_ or similar.

I think with the V2 we shoudn't be constrained to V1 ideas.. I would extend that to the syntax we use in the payload... If there is a better way of structuring the JSON (or other payloads being sent) then we should do it. Now is the time to make a big change to our indexing patterns. Having said that, this PR ended up starting to be a big job, so that is why it's DRAFT on hold. But I would love any improvements you make to this PR!

@janhoy

janhoy commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

I think with the V2 we shoudn't be constrained to V1 ideas.

That's a good point, in V2 we can also change the atomic update contract to make it NOT ambigoous. Maybe there is a genuinely better way, maybe something refencing a field with jsonPath and the operation with some operation object?

@janhoy

janhoy commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

I did #4910 to test the ND-JSON idea. No atomic support, keep it simple.

@epugh

epugh commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

@janhoy I am going to address your feedback, and try to do them as cohesive commits, that way, if we want to pull any of them into brand NEW PR's so that this current PR can be mergable, we can. I know I struggle with scope creep a lot. Especially wihtout a good sense of "can I record "and this doesn't work"" and still make this a legit migration.

Also, if when you see my next steps, you think "Lets break this up into X PR's" we can... If we want the js changes for example to be in their own PR.

@epugh

epugh commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Two asks before merge:

  1. partial-document-updates.adoc should say explicitly that atomic syntax works on /update only, and that /update/json will silently destroy the document.
  2. Worth a follow-up JIRA to make /update/json reject atomic-operator maps rather than index them.

The single-object-vs-array asymmetry on /update is also worth smoothing over eventually — {"id":...} failing with Unknown command 'id' is a rough first experience.

I dealt with this in ac83841

@epugh

epugh commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Or we keep the adds, deletes, deleteByQuery:

My friend Claude has this to say about touched_docs vs keeping the three: I'd keep adds / deletes / deleteByQuery rather than collapsing to touched_docs. They aren't redundant: a delete-by-query key is a query, not an id, and merging them loses that distinction — which is exactly why the PR needed both VersionedDocument and VersionedQuery.

Love it! Thanks for thinking about it.

…gh/solr into copilot/migrate-update-api-to-jax-rs
…ong typed responses.

We may need to think about how update model objects have some sort of "escape" for when random third party Update Processors ADD to the repsonse.   Rihgt now in this PR, we actually add strong typing....   Which may be fine, but what if we have processors dynamically loaded?
…out everything but metrics

Committing as single chunk for furthur reveiw and feedback.
@janhoy

janhoy commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

If we want the js changes for example to be in their own PR.

I'd say yes to this. When we approach completion, ask LLM to extract all frontend edits into its own branch/PR, stacked on top of this one, and commit separately.

That said, I do appreciate using UI changes as a test bench for actually testing the generated JS client and vetting the API changes, that is how it should be done.

@epugh

epugh commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

If we want the js changes for example to be in their own PR.

I'd say yes to this. When we approach completion, ask LLM to extract all frontend edits into its own branch/PR, stacked on top of this one, and commit separately.

That said, I do appreciate using UI changes as a test bench for actually testing the generated JS client and vetting the API changes, that is how it should be done.

in the vein, we could also pull out the use of techproducts in the examples into their own PR as well...

Or you give a +1 and we merge this ;-)

@janhoy
janhoy marked this pull request as draft November 10, 2026 19:38
@janhoy

janhoy commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

@epugh I'm going to push a commit bringing this PR up to date with main, so we can continue working on it. I will likely merge #4910 first since that is ready, but the conflict is not that complex. Also, there are 10 unresolved review comments in this PR, do you want to close the ones that are truly resolved?

# Conflicts:
#	solr/solr-ref-guide/modules/indexing-guide/pages/indexing-with-update-handlers.adoc
#	solr/solr-ref-guide/modules/indexing-guide/pages/partial-document-updates.adoc
#	solr/solr-ref-guide/modules/indexing-guide/pages/transforming-and-indexing-custom-json.adoc
#	solr/webapp/web/js/angular/controllers/core-overview.js
#	solr/webapp/web/js/angular/controllers/cores.js
#	solr/webapp/web/js/angular/services.js
…ateTool

The generated SolrJ request gained a contentType argument on this branch,
and bin/solr create started using it on main (SOLR-18320).
…entions

{collectionName} placeholders, quoted URLs, --data-binary for update payloads,
and one command per line, as standardized on main by SOLR-18460, SOLR-18495
and SOLR-18500.
@janhoy

janhoy commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

🤖 Claude Fable 5 merged upstream/main into this branch (220 commits behind) and resolved the conflicts. Follow-up commits on top of the merge:

Conflicts resolved in the merge commit

  • indexing-with-update-handlers.adoc, partial-document-updates.adoc, transforming-and-indexing-custom-json.adoc (20 hunks): this PR rebuilt the examples into V1/V2 tab blocks while main standardized every curl example (SOLR-18460, SOLR-18495, SOLR-18500). Took this PR's structure in every hunk; the text main had edited in place (posting XML from a file, response format) already lives elsewhere in the PR's restructured pages.
  • core-overview.js, cores.js: took main's updated injection lists ($timeout, ReplicationV2, NodeTarget, ApiErrorHandler) minus the Update service this PR removes.
  • services.js: dropped the Update $resource factory as this PR does, kept main's new ParamSetsV2 factory next to it.

Semantic conflicts, 8afa6bd and the commit after it: the api.mustache change on this branch gives every generated multi-content-type request a contentType argument. Callers added on main since this PR branched (bin/solr create from SOLR-18320, TestDistribFileStore, ConfigsetPinnedPackageVersionTest) now pass application/zip or application/octet-stream, matching this PR's own adaptations. UpdateAPIMetricsDoubleCountingTest imports ContentStreamBase from its new home in solr-core (SOLR-18351).

Curl conventions, b98bc2f: swept the three pages above to main's current style: {collectionName} placeholders, quoted URLs, --data-binary for update payloads, one command per line. Mechanical, no wording changes.

Validated locally with compileTestJava, the Admin UI browser suite (-Ptests.selenium=true) and the Ref Guide build with link check.

@janhoy
janhoy marked this pull request as ready for review November 10, 2026 21:08
@github-actions

Copy link
Copy Markdown

Automated PR readiness judgements (LLM-generated; may be wrong; not updated on later pushes).

  • ✅ Jira issue in title: already present
  • ✅ Changelog: type: changed
  • ✅ Reference Guide update: included
  • ⚠️ Upgrade notes — missing (confidence: medium): The V2 javabin endpoint path changes from /​update/​bin to /​update/​javabin, and the Admin UI no longer supports targeting custom update handlers, which are backwards-incompatible changes for users relying on the old paths or UI behavior.​
  • ✅ Tests: included

@janhoy janhoy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick browse through all files...

throws SolrServerException, IOException {
try (final var stream = new ByteBufferInputStream(buffer)) {
final var uploadReq = new FileStoreApi.UploadFile(name, stream);
final var uploadReq = new FileStoreApi.UploadFile(name, stream, "application/octet-stream");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a constant in place of string?

var uploadReq =
new ConfigsetsApi.UploadConfigSet(
confName, new ByteArrayInputStream(Utils.zipDirectory(confPath)));
confName, new ByteArrayInputStream(Utils.zipDirectory(confPath)), "application/zip");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constant instead of string? Either from Jetty or from JDK

// Create and execute the upload request using generated client
final var uploadRequest =
new ConfigsetsApi.UploadConfigSet(configSetName + suffix, fileBytes);
new ConfigsetsApi.UploadConfigSet(configSetName + suffix, fileBytes, "application/zip");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constant? Also line 1283

final var uploadReq = new FileStoreApi.UploadFile(name, new ByteBufferInputStream(buffer));
final var uploadReq =
new FileStoreApi.UploadFile(
name, new ByteBufferInputStream(buffer), "application/octet-stream");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constant?

Increments or decrements a numeric field's value by a specific amount, specified as a single integer or float.
Positive amounts increment the field's value, and negative decrement.

NOTE: Atomic update syntax is supported exclusively on the `/update` endpoint.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember why this was. And is it precise to say "exclusively on the /update endpoint", when the /update endpoint now is a dispatcher to various sub handlers, which behave quite differently. It is only the json one with Content-Type: application/json that handles atomic update???

[source,bash]
----
curl 'http://localhost:8983/api/collections/techproducts/update/json/docs'\
curl 'http://localhost:8983/api/collections/techproducts/update/json'\

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a pity that v2 paths changelike this between v1 and v2 but I guess we considered all alternatives...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants