I adapted the SPARQL Explorer “Snorql,” originally built for Virtuoso / Dydra, to work with Apache Jena Fuseki. While SPARQL is a W3C standard, the behavioral differences between endpoint implementations can be surprisingly large. Here I document the three issues I encountered during Fuseki adaptation and their solutions.
Development Environment
I started Fuseki with Docker and tested locally.
1. DESCRIBE Response Format Differs
Symptom
When sending a DESCRIBE query to Fuseki, results were not displayed on screen. The console showed JSON parse errors.
Investigation
SPARQL’s DESCRIBE / CONSTRUCT queries, unlike SELECT, return RDF graphs. The format differs by endpoint.
| Endpoint | DESCRIBE Response | output= parameter |
|---|---|---|
| Virtuoso / Dydra | SPARQL Results JSON (proprietary extension) | — |
| Fuseki | RDF format (Turtle, RDF/JSON, etc.) | Takes priority over Accept header |
Snorql had been appending output=json as a URL parameter. Fuseki interprets this and returns JSON-LD, but Snorql expects RDF/JSON, causing parsing to break.
Using curl to isolate the cause makes it clear.
The output=json parameter was interfering in Fuseki. Relying on Accept header content negotiation returns the correct format.
Fix (5 locations)
sparql.js — Conditional sending of output parameter
If _output is an empty string, output= is no longer sent.
sparql.js — Detect JSON via Content-Type
Even when the output parameter is not sent, if the response Content-Type contains json, it uses JSON.parse. This handles the case where Fuseki returns application/rdf+json.
snorql.js — Accept header and output control for DESCRIBE
Fixed the typo (jsonapplication/json to ,application/json) and set output to an empty string to enable Fuseki’s content negotiation.
snorql.js — Properly propagate empty string output
JavaScript’s if(output) evaluates the empty string "" as falsy, so it was changed to !== undefined.
snorql_def.js — endpoint_type setting
2. Queries Fail Without PREFIX Declarations
Symptom
DESCRIBE now displays correctly, but label retrieval and related resource searches return 400 errors.
Cause
After displaying DESCRIBE results, Snorql internally issues auxiliary queries for label retrieval, etc. These use prefix names like rdfs:label and schema:name directly.
| Endpoint | PREFIX Declaration |
|---|---|
| Virtuoso | Implicitly resolves rdfs:, etc. (no declaration needed) |
| Fuseki | Requires explicit PREFIX declarations for all (W3C compliant) |
Code that relied on Virtuoso’s implicit resolution did not work with Fuseki.
Fix
snorql_ldb.js — Auto-prepend PREFIX to auxiliary queries
By registering namespaces with SPARQL.Service.setPrefix(), PREFIX rdfs: <...> is automatically prepended to queries when sent. All prefixes defined in namespaces.js are registered at once.
snorql_ldb.js — null guard
0
This prevents TypeError when undefined properties are passed as undefined.
3. The is_virtuoso Flag Was Serving Two Purposes
Symptom
When trying to run Dydra alongside Fuseki, 400 errors occurred on Dydra.
1
Cause
The is_virtuoso flag was serving two different responsibilities.
| Responsibility | Location | When truthy |
|---|---|---|
| Response format | snorql.js | Sends output=json |
| Virtuoso-specific syntax | util.js | Appends define sql:describe-mode "CBD", etc. |
Dydra needs output=json (same response format as Virtuoso) but does not accept Virtuoso-specific define directives. Making is_virtuoso truthy enabled both, causing an error from define.
Fix: Separation of Responsibilities
The single flag was split into three settings.
| Setting | Role |
|---|---|
endpoint_type | ON/OFF for Virtuoso define directives |
is_virtuoso | Response format (output=json vs Accept header) |
describe_as_construct | Auto-convert DESCRIBE to CONSTRUCT |
util.js — Use endpoint_type for define directives
2
util.js — DESCRIBE to CONSTRUCT auto-conversion
3
JPS’s Virtuoso returns a server error (SR452) for DESCRIBE, so an option was added to convert DESCRIBE <uri> to an equivalent CONSTRUCT.
Settings for Each Endpoint
4
Data Flow (After Fix)
5
Summary
SPARQL is a standard specification, but the differences between implementations are surprisingly numerous.
| Difference | Virtuoso | Fuseki |
|---|---|---|
| DESCRIBE response | Proprietary JSON | W3C-compliant RDF format |
| PREFIX resolution | Implicit resolution | Explicit declaration required |
Proprietary extensions (define) | Yes | No (causes parse error) |
To absorb these differences, the dual responsibility of is_virtuoso was separated into three settings: endpoint_type, is_virtuoso, and describe_as_construct. When adding a new endpoint type, you only need to add flags to snorql_def.js without breaking existing behavior.
For discovering these differences, checking Content-Type with curl and the browser’s Network tab were most useful. Even with SPARQL that should be the same according to the specification, looking at response headers reveals the individuality of each implementation.