After upgrading a Hot Chocolate service from 15 to 16, what changes at startup and in how schema components get application services?
answer
- no more waiting for the first request
- two service providers
- warmup tasks replace the old helper
- cross-register for filters and listeners
- request batching now opt-in
basics
~20 sVersion 16 builds the schema and executor at startup, so schema errors fail boot and InitializeOnStartup is gone; error filters and diagnostic listeners see only the application services cross-registered with AddApplicationService<T>(); and request batching is off by default.
solid answer
~40 sThree things bite on a 15-to-16 upgrade. **Eager initialization**: the schema and request executor are built by a hosted service before the server takes traffic, so a broken schema now stops startup instead of the first request. `InitializeOnStartup` is removed; its warmup delegate moves to `AddWarmupTask(...)`, and `ModifyOptions(o => o.LazyInitialization = true)` opts out. **Service separation**: components activated by Hot Chocolate — `AddErrorFilter`, `AddDiagnosticEventListener`, `AddHttpRequestInterceptor` and others — are built from the schema's own service provider, so an application service they need must be cross-registered with `.AddApplicationService<T>()`, which resolves it once and holds it as a singleton. Resolver parameter injection is unaffected. **Batching**: request batching is disabled; `ModifyServerOptions(o => o.Batching = AllowedBatching.All)` re-enables it, with `MaxBatchSize` 1024.
code
csharp · 11 linesbuilder.AddGraphQL()
.AddTypes()
.AddApplicationService<OrderAuditLog>()
.AddErrorFilter<BookstoreErrorFilter>()
.AddWarmupTask(async (executor, ct) =>
{
var request = OperationRequestBuilder.New()
.SetDocument("{ __typename }")
.Build();
await executor.ExecuteAsync(request, ct);
});go deeper
Recall that Hot Chocolate 16 builds the schema at startup and that request batching is now off by default.
Explain the migration steps: remove InitializeOnStartup, use AddWarmupTask, and cross-register services for error filters and listeners.
Diagnose startup failures after the upgrade, keep AddApplicationService to singleton-safe services, and separate request batching from DataLoader batching.
Plan the upgrade across services: boot-time validation in deployment checks, contract changes from scalar renames, and whether batching is enabled for any client.
## Why this upgrade gets its own interview question Most Hot Chocolate upgrades are renames. Version 16 changes **when** the schema is built and **which service provider** its components come from, and both show up as runtime failures rather than compile errors. Knowing them separates someone who has run the upgrade from someone who read the headline. ## 1. The schema is built at startup In version 15 and earlier, the schema and request executor were built **lazily**, on the first request, unless you called `InitializeOnStartup`. Version 16 makes **eager initialization** the default: a hosted service builds the executor during application startup, before the server begins accepting traffic. Consequences: - A schema error — such as a type that cannot be resolved or an invalid default value — now **fails startup** instead of the first request. That is the point: the error surfaces in development and in the deployment's readiness check. - `InitializeOnStartup` **is removed**. Its `warmup` delegate moves to `AddWarmupTask((executor, ct) => ...)`. Warmup tasks run at startup and, by default, again whenever the schema is rebuilt at runtime. - Startup takes longer for large schemas; if something really needs the old behaviour, `ModifyOptions(o => o.LazyInitialization = true)` opts out. The migration guide calls that rarely recommended. This fits container deployments well: a broken schema shows up as a failed start in the deployment, not as errors on the first real requests after traffic has already moved to the new instance. Version 16 also validates argument and input-field **default values** against their types when the schema is built, so a bad default that used to fail only when used now fails at startup too. ## 2. Schema services and application services are separated Hot Chocolate keeps a **second `IServiceProvider`** per schema for its internal services. Before 16, components such as error filters were activated from a combined provider that could see your application services. In 16, configuration is instantiated **only from the schema service provider**, so an application service must be **cross-registered**: ```csharp builder.Services.AddSingleton<OrderAuditLog>(); builder.AddGraphQL() .AddApplicationService<OrderAuditLog>() .AddErrorFilter<BookstoreErrorFilter>(); ``` What to remember: - It applies to components Hot Chocolate activates, including `AddErrorFilter`, `AddDiagnosticEventListener`, `AddHttpRequestInterceptor`, `AddSocketSessionInterceptor` and `AddOperationCompilerOptimizer`. Even `ILogger<T>` for a listener must be cross-registered. - `AddApplicationService<T>()` resolves the service **once**, during schema initialization, from the application provider and registers it as a **singleton** in the schema provider — cross-register singleton-safe services, not a scoped `DbContext`. - **Resolver injection is not affected.** A resolver parameter of type `BookstoreContext` is still resolved per resolver scope for queries (`DependencyInjectionScope.Resolver`) and per request for mutations. - `schemaServices.GetRootServiceProvider()` reaches the application provider from inside the schema provider when needed. ## 3. Request batching is off **Request batching** — several GraphQL operations in one HTTP request — was on by default in 15 and is **disabled** in 16 as a security measure. Server options moved to the schema level: ```csharp builder.AddGraphQL() .ModifyServerOptions(o => o.Batching = AllowedBatching.All); ``` `EnableBatching` became the `Batching` flags property, and `MaxBatchSize` caps operations per batch at 1024 by default. This is unrelated to **DataLoader** batching, which is unchanged. ## Other changes worth a sentence | Area | 15 | 16 | |---|---|---| | JSON scalar | `Json` | merged into `Any` | | `TimeSpan` scalar name | `TimeSpan` | `Duration` | | `byte[]` | `ByteArray` | `Base64String` | | Constrained scalars such as `NonNegativeInt` | built in | removed | | `Page<T>` | constructible with `new` | abstract; `Page<T>.Create` | | Filter operations per `where` | no default limit | 64 by default | | Resolvers on `[ExtendObjectType]` classes | backing member projected | not projected unless opted in | ## An upgrade checklist 1. Update **all** `HotChocolate.*` packages to the same 16.x version. 2. Delete `InitializeOnStartup`; move warmup logic to `AddWarmupTask`. 3. Start the app and fix every schema error it now reports at boot. 4. For each error filter, listener and interceptor, add `AddApplicationService<T>()` for its dependencies. 5. Decide deliberately whether any client needs request batching before enabling it. 6. Search the schema diff for renamed scalars; clients that send `Json` or `TimeSpan` literals see a contract change. 7. Re-run projection tests: a resolver on an `[ExtendObjectType]` class that reads a parent member now needs `[BindMember]` or `[IsProjected(true)]` to keep that member projected.
- An error filter that logs through ILogger<T> throws at startup after the Hot Chocolate 16 upgrade; why?Version 16 activates the error filter from the schema service provider, which does not contain application services. The logger lives in the application provider, so it must be cross-registered: `.AddApplicationService<ILogger<BookstoreErrorFilter>>()`. Resolver parameters would not need this, because resolver injection still uses the application's request and resolver scopes.
- Is Hot Chocolate 16's disabled request batching the same thing as DataLoader batching?No. Request batching lets one HTTP request carry several GraphQL operations; version 16 turns it off by default and re-enables it through `ModifyServerOptions` with `AllowedBatching`. DataLoader batching combines keys requested by resolvers inside one operation into one fetch, and it is unchanged by this setting.
saying these in an interview costs you the question
- Hot Chocolate 16 still builds the schema lazily on the first request.
- InitializeOnStartup is still the way to warm up the executor in 16.
- AddApplicationService is needed before any resolver can inject a DbContext.
- Cross-registering a scoped DbContext with AddApplicationService is harmless.
- Disabling request batching in 16 also disables DataLoader batching.