The JSON Request API
|
This section documents the current Solr line (10.0; 9.10.x the maintained 9.x branch), written and verified against the Apache Solr Reference Guide. No specific patch version is pinned. Some capabilities (the Solr Operator on Kubernetes, the package-manager ecosystem, Learning To Rank model training, and expert plugin development) are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Every example on the preceding pages built a request as a URL query string — q=…&fq=…&sort=… — appended to /select. That works for a quick curl, but application
code that assembles query strings by hand runs into the same problems URL-encoded request bodies
always do: nesting is awkward, boolean structure has to be flattened into repeated fq parameters,
and a query built from untrusted input is one missed escape away from a broken (or manipulable)
request. The JSON Request API replaces the query string with a single JSON document POSTed to the
handler, and the JSON Query DSL gives that document’s query/filter keys the same expressive
power as the standard, DisMax, and eDisMax parsers — structured as nestable objects instead of \{!…} local-params strings. This page covers both, plus
ParamSets and the Request Parameters API for storing shared parameter sets server-side instead of
repeating them on every request.
Sending a JSON request
POST a JSON body to the same /select handler used for GET requests; Solr merges it with any query
parameters on the URL (JSON body wins on conflict) and runs the search exactly as it would from a
query string.
curl "http://localhost:8983/solr/books/select" \
-H 'Content-Type: application/json' \
-d '{
"query": "title:darkness",
"filter": "genre_ss:science fiction",
"limit": 10,
"sort": "published_at desc"
}'
# https://solr.apache.org/guide/solr/latest/query-guide/json-request-api.html
query and filter accept a plain query string (parsed by the default lucene parser, or by
whatever defType says), an explicit local-params string, or — the point of this page — a JSON
object naming a parser and its arguments. limit/offset/sort/fields/facet are JSON spellings
of rows/start/sort/fl/json.facet; see
Query basics & parameters for what each one
does at the parameter level — the JSON API changes how you write a request, not what the
underlying parameters mean.
JSON Query DSL
A query (or filter) value can be a JSON object whose single key names a query parser and whose
value carries that parser’s arguments — the JSON equivalent of a \{!parser arg=val} local-params
string, without the string-escaping problems of building one by hand.
{
"query": {
"edismax": {
"query": "darkness",
"qf": "title^2 author",
"mm": "75%"
}
}
}
curl "http://localhost:8983/solr/books/select" \
-H 'Content-Type: application/json' \
-d '{ "query": { "edismax": { "query": "darkness", "qf": "title^2 author", "mm": "75%" } } }'
# https://solr.apache.org/guide/solr/latest/query-guide/json-query-dsl.html
bool, boost, and lucene clauses
bool composes other clauses — JSON objects, local-params strings, or plain query strings, mixed
freely — with must and filter in query context and must_not in negated filter context, the
same query/filter split covered on
Query basics & parameters:
{
"query": {
"bool": {
"must": [ { "lucene": { "df": "title", "query": "darkness" } } ],
"filter": [ "genre_ss:science fiction", { "lucene": { "query": "in_stock_i:[1 TO *]" } } ],
"must_not": [ "status:withdrawn" ]
}
}
}
lucene is the JSON form of the standard query parser (df/q.op/query arguments); it is what a
bare query string is shorthand for. boost wraps a query and multiplies its score by a function, the
JSON equivalent of the boost local-params parser used in relevance tuning — see
Relevance & scoring for scoring in depth and
Function queries for what can appear as the boost function
itself:
{
"query": {
"boost": {
"query": { "lucene": { "query": "title:darkness" } },
"b": "recip(ms(NOW,published_at),3.16e-11,1,1)"
}
}
}
curl "http://localhost:8983/solr/books/select" \
-H 'Content-Type: application/json' \
-d '{
"query": {
"bool": {
"must": [ { "lucene": { "df": "title", "query": "darkness" } } ],
"filter": [ "genre_ss:science fiction" ],
"must_not": [ "status:withdrawn" ]
}
}
}'
# https://solr.apache.org/guide/solr/latest/query-guide/json-query-dsl.html
Nested and named queries
A clause can nest another clause of any kind — a bool inside a boost inside a filter, as deep
as the logic needs — because every clause is just a JSON value in the same DSL, not a separate
sub-language. The queries key declares named queries alongside query/filter; a clause elsewhere
in the same request references one by name instead of repeating it:
{
"queries": {
"in_print": "status:active"
},
"query": {
"bool": {
"must": [ "title:darkness" ],
"filter": [ { "param": "in_print" } ]
}
}
}
Reusing a named query this way keeps a shared condition — an availability filter applied everywhere
a request needs it — written once per request instead of copy-pasted into every clause that needs
it. Tagging a clause with a leading #tagName inside its parser object works the same as \{!tag=…}
in a local-params string, most commonly to exclude a filter from one facet’s counts; see
Faceting for tagging and excluding filters from facet computation.
The params block: the escape hatch
Not every request parameter has a dedicated JSON key. The params block holds anything else — values placed there behave exactly as if they had been appended to the query string, so any
parameter documented on Query basics &
parameters or a component-specific page works inside it without waiting for the JSON DSL to grow a
matching key:
curl "http://localhost:8983/solr/books/select" \
-H 'Content-Type: application/json' \
-d '{
"query": "title:darkness",
"params": {
"defType": "edismax",
"qf": "title^2 author",
"hl": true,
"hl.fl": "title"
}
}'
# https://solr.apache.org/guide/solr/latest/query-guide/json-request-api.html
params is also where multiple JSON bodies (or a JSON body plus URL parameters) get merged when a
request arrives from more than one source at once — later values win, giving predictable precedence
instead of a silent pick between conflicting sources.
ParamSets and the Request Parameters API
A params block (or a URL query string) still repeats the same parameters on every request from
every client. The Request Parameters API stores a named set of parameters — a ParamSet — once,
server-side in params.json (held in ZooKeeper for SolrCloud, in the collection’s conf directory
otherwise), and lets any request pull it in by name with useParams instead of restating it.
# Create (or update) a ParamSet named "myFacets".
curl "http://localhost:8983/solr/books/config/params" \
-H 'Content-Type: application/json' \
-d '{ "set": { "myFacets": { "facet": "true", "facet.field": "genre_ss", "facet.limit": 10 } } }'
# Apply it to a request -- equivalent to adding facet=true&facet.field=genre_ss&facet.limit=10.
curl --get "http://localhost:8983/solr/books/select" \
--data-urlencode 'q=*:*' --data-urlencode 'useParams=myFacets'
# View what's stored.
curl "http://localhost:8983/solr/books/config/params/myFacets"
# https://solr.apache.org/guide/solr/latest/configuration-guide/request-parameters-api.html
A ParamSet can hold three kinds of entries: plain defaults (used when the request does not supply the
parameter), invariants (win even when the request does supply the parameter, e.g. to pin rows
or fq a client cannot override), and appends (added alongside whatever the request sent, e.g. an
extra fq that always applies). useParams accepts a comma-separated list, later sets overriding
earlier ones, so a request can layer a shared baseline ParamSet under a request-specific one. A
request handler’s own solrconfig.xml defaults/invariants/appends can likewise be moved into a
ParamSet and referenced from useParams in the handler definition, so shared configuration changes
without an solrconfig.xml edit or core reload.
Why the JSON API supersedes hand-built query strings
For application code — as opposed to an ad hoc curl — the JSON Request API is the better default:
-
No hand escaping. A query string built by string concatenation has to URL-encode every value and correctly nest
\{!…}local-params syntax inside it; a JSON body is built (and parsed) with the same JSON library the rest of the application already uses, so nesting and escaping are handled by the serializer, not by hand. -
Structure matches intent. A
boolquery’smust/filter/must_notarrays are the actual boolean structure of the request; the query-string equivalent — severalfqparameters plus aqstring with embedded boolean operators — has to be reconstructed by the reader. -
Programmatic composition. Building a query object up from conditionals in code (add this filter only if the user supplied a category, add that boost only in one code path) is ordinary object manipulation; the same logic against a query string means careful, error-prone concatenation.
-
Reach beyond
q/fq. The JSON Facet API (Faceting) is only reachable through thejson.facetparameter or thefacetkey of a JSON request body — there is no equivalent query-string syntax for its nested, multi-level facets. -
One document, one diff. A JSON request body is a single value a test can assert against or a code review can diff line-by-line; a query string is a flat, order-sensitive list of key/value pairs.
None of this replaces the query parsers themselves — lucene,
dismax, edismax, and the rest still do the actual parsing and matching. The JSON Request API only
changes the transport: the same parser arguments that would sit in a \{!…} local-params string or a
query-string parameter instead sit as JSON, sent as the request body rather than assembled into a URL.
Continue with Query parsers for what each parser (lucene,
dismax, edismax, and the rest) actually does with these arguments, or
Faceting for the JSON Facet API this same request body carries under
its facet key.