← Back to blog

Spark FHIR Server 3.0

One of the major goals of the 3.0 release was to consolidate as much code as possible so that adding support for additional FHIR versions would be more trivial. The 3.0 release is still only supporting STU3 and R4, the plan is to include R4B, R5, and R6 in the 3.1 release. Also worth mentioning that as of this posting R6 is in its 4th ballot phase and is expected to be released later this year.

Support for .NET target netstandard2.0 has been dropped which means .NET Framework 4.6.1 and later is no longer supported. This enabled us to remove a bunch of code which we did not have a good testing harness for.

Currently bug fixes and some minor/useful features are backported to version 2.x. You can find a list at the end of the post describing which parts has been backported.

An experimental background indexing mode has been added as an opt-in. There is uncertainty to its usefulness so it might be removed in the future, if this is something that you find useful we will need feedback on it. Documentation can be found here

You can now join our Discord server: https://discord.gg/M9kp2zGGYF.

When migrating the first thing you will notice is that AddFhir(...), UseFhir(...), and AddFhirFacade(...) has been renamed to AddFhirWithMvc(...), UseFhirWithMvc(...), and AddFhirFacadeWithMvc(...).

For further details on migrating from v2 to v3 look up the guide. Also make sure that you read through the What's New in 3.0 section below.

What's New in 3.0

1. Unified Spark.Engine and Spark.Store.MongoDB Shared Libraries

PRs: #1171, #1182, #1183, #1184, #1185, #1176

A new NuGet package Spark.Engine has been introduced, containing all non-version-specific engine code. Spark.Engine.R4 and Spark.Engine.STU3 are now thin satellite assemblies that sit on top of it. Consumers should continue referencing the version-specific package, the shared package is an implementation detail that makes implementing future versions of FHIR much easier.

As part of this split, Spark.Mongo.R4 and Spark.Mongo.STU3 has been consolidated and renamed to Spark.Store.MongoDB, making the MongoDB store equally version-agnostic.

2. R4 and STU3 in One Branch

PRs: #1202, #1169

The master branch is now the single source of truth for both R4 and STU3, consolidating CI, Docker images, and integration tests.

3. New Solution Structure

PRs: #1187, #1188, #1189, #1190

The repository has been reorganised into:

Libraries/    - Spark.Engine, Spark.Engine.R4, Spark.Store.MongoDB
Applications/ - Spark.Web.R4, Spark.Web.STU3
Tests/        - unit tests + IntegrationTests/

4. Modernised .NET Target Framework Support

PRs: #842, #843, #898, #917, #1036

The targets netstandard2.0 and net472 have been dropped. Spark 3.0 targets net8.0, net9.0, and net10.0. This allows the library to take advantage of modern C# language features and runtime improvements throughout.

5. Upgraded to Hl7.Fhir SDK 6.1.1

PR: #1177

All projects have been upgraded to Firely SDK 6.1.1, including full API migration to the new SDK surface.

6. Experimental Background Indexing

PRs: #1106, #1108

A background indexing pipeline has been introduced as an opt-in feature. By setting SparkSettings.Experimental.IndexingMode = Background, write operations enqueue index work onto an IIndexQueue (backed by a MongoDB indexqueue collection) instead of indexing synchronously in the HTTP request path.

New types:

  • IIndexQueue - queue interface (EnqueueAsync, ClaimNextAsync, AcknowledgeAsync, NackAsync)
  • IndexWorker - BackgroundService that drains the queue via IIndexService
  • MongoIndexQueue - MongoDB implementation
  • IndexQueueSettings - controls LeaseTimeout, MaxAttempts, PollInterval
  • ExperimentalSettings / IndexingMode enum on SparkSettings

7. FHIR Facade Decoupled from MVC

PR: #1262

The FHIR service layer can now be registered independently of MVC:

  • AddFhirFacadeCore(...) - registers the FHIR core services only (useful for minimal APIs or non-MVC hosts)
  • AddFhirFacadeWithMvc(...) - full MVC pipeline (renamed from AddFhirFacade)
  • AddFhirWithMvc(...) - renamed from AddFhir
  • UseFhirWithMvc(...) - renamed from UseFhir

8. Generic FhirResponse<T>

PR: #850

FhirResponse<T> wraps FhirResponse with a typed resource parameter, enabling compile-time safety when working with specific FHIR resource types. Matching generic overloads have been added to IFhirService and IFhirResponseFactory.

9. Enriched IFhirModel

PRs: #1033, #1046, #1048, #1171

Several members have been added to IFhirModel to reduce the surface area where version-specific code needs to leak into shared code:

  • SupportedResources - IReadOnlyList<string> of all resource type names
  • FhirRelease - FHIR version string (e.g. "4.0.1"), moved here from SparkSettings
  • GetModelInspector() - returns the ModelInspector for class-mapping lookups
  • GetTypeForFhirType(string) - maps FHIR type name → C# type
  • GetFhirTypeNameForType(Type) - maps C# type → FHIR type name
  • SearchParameters now returns List<Spark.Engine.Model.SearchParameter>

10. CapabilityStatement Builder Overhaul

PRs: #1200, #1201

The CapabilityStatement builder family has had an overhaul and are living in the the shared Spark.Engine library. The builder API has been updated to use fluent Func<TBuilder, TBuilder> delegates, and CapabilityStatementService now accepts ServerVersion and FHIRVersion in its constructor. The static legacy builder in Service.FhirServiceExtensions.CapabilityStatementBuilder has been removed.

11. ServerVersion Record

Commit: #44687ed3

SparkSettings.Version has changed from string to a new ServerVersion record (Major, Minor, Patch, optional PreRelease). An implicit operator string ensures backward-compatible use anywhere a string was expected.

12. Custom Search Parameters via SparkSettings

Commit: #5c755407

Custom search parameters must now be configured through SparkSettings.CustomSearchParameters. The old AddCustomSearchParameters() extension method has been removed.

13. New React + Vite Admin Frontend

PR: #1096

The ASP.NET MVC views, Entity Framework Core infrastructure, and ASP.NET Identity have been removed and replaced with a React + TypeScript + Vite single-page application. Authentication is now handled via GitHub OAuth, removing the need to manage a local user database.

The frontend is built automatically during dotnet build (MSBuild runs npm run build inside app/), so no separate build step is required.

14. UUID v7 Identity Generation

PR: #1137

GuidIdentityGenerator now generates UUID v7 identifiers instead of random UUID v4. UUID v7 is time-ordered, which improves MongoDB index locality and query performance for sequential inserts.

15. Non-FHIR API Support

PR: #945

The engine can now coexist with custom non-FHIR endpoints on the same host. Previously, the FHIR routing and middleware could interfere with unrelated controllers. 3.0 provides a clean separation so that a mixed application hosting both FHIR resources and custom REST endpoints works correctly out of the box.

16. Improved Search Error Handling

PRs: #1161, #1266, #1270

  • Malformed search parameters now produce an OperationOutcome entry in the response bundle instead of throwing an exception.
  • Unknown query parameters result in an OperationOutcome entry in the bundle (instead of silently ignoring them or failing).

17. _count=0 Treated as _summary=count

PRs: #1125, #1126

Sending _count=0 on a search request now correctly returns a count-only result bundle, consistent with the FHIR specification's intent for _summary=count.

18. Weak ETag Support

PR: #1091

The server can now parse the weak ETag format (e.g. W/"123") in addition to the strong ETag format.

19. Version-Guarded Conditional Search Index Writes

PR: #1108

When an out-of-order write is detected (e.g. a delayed re-index of an older resource version), the index store now guards against overwriting a newer index entry with an older one. This prevents stale data from corrupting search results during concurrent or background indexing scenarios.

20. Content-Type Header Handling Improvement

PR: #906

The engine now correctly handles Content-Type headers that include additional parameters (e.g. application/fhir+json; charset=utf-8). Previously, extra parameters could cause the content type to go unrecognised and result in a 415 Unsupported Media Type response.

21. Crash Fix: Contained Resources Without an Id

PR: #923

A crash that occurred when a resource contained more than one contained sub-resource lacking an id element has been fixed. The server now handles this gracefully and continues processing instead of throwing an unhandled exception.

22. Other Notable Fixes & Improvements

  • IfMatchVersionId is now correctly propagated to the Entry.VersionConstraint (#1263)
  • Zero-length response body is guarded against
  • Exceptions are always logged in the error handler (no silent swallowing)

Breaking Changes (Summary)

Consult the migrate from v2 to v3 guide for all breaking changes.

The headline breaking changes are:

  • Removed legacy .NET targets (netstandard2.0, net472)
  • AddFhirFacadeAddFhirFacadeWithMvc
  • AddFhirAddFhirWithMvc
  • UseFhirUseFhirWithMvc
  • Spark.Mongo.R4 package → Spark.Mongo
  • Many IFhirModel method signatures changed to use Spark.Engine.Model.SearchParameter instead of Hl7.Fhir.Model.SearchParameter
  • Namespace consolidation - many types moved to Spark.Engine.* namespaces
  • SparkSettings.FhirRelease removed - use IFhirModel.FhirRelease
  • Legacy formatter infrastructure removed (BinaryFormatter, FhirMediaTypeFormatter, etc.)
  • Snapshot sealed - many previously public members are now private

Features Backported to v2 (v2-r4/master and v2-stu3/master)

The following improvements were made available to the v2 line without requiring an upgrade to v3. Both v2-r4/master and v2-stu3/master.

Feature Released as
Non-FHIR API support alongside the FHIR server v2.3.5
Crash fix: contained resources without an id element v2.3.5
SignalR Maintenance Hub requires authorization v2.3.5
Weak ETag parsing v2.4.0
React + Vite frontend, GitHub OAuth, removal of legacy MVC/Identity/EF Core v2.4.0
Target .NET 10.0 (alongside net8 / net9) v2.4.0
UUID v7 in GuidIdentityGenerator v2.4.0
_count=0 treated as _summary=count v2.4.0
Optimised Docker Dockerfile layer caching v2.4.0
OperationOutcome entry in bundle for unknown query parameters v2.4.1
IfMatchVersionId propagated to Entry.VersionConstraint v2.5.0
ParseVersionFromETag() extraction v2.5.0
Malformed search parameters → OperationOutcome entry instead of exception v2.5.0