#3341·quartznet

Trim and AOT compatibility track — what remains

Author: lahmaCreated Aug 22, 2026Updated Sep 3, 2026

The eight-step sequence this issue opened with is done. Quartz sets IsTrimmable, IsAotCompatible, and the trim, AOT and single-file analyzers, with TreatWarningsAsErrors; ObjectUtils is gone and the fire path carries [DynamicallyAccessedMembers] end to end; the HTTP wire contract, the store's JSON blobs and the Quartz configuration section are all source-generated; and Quartz.Trimming.Canary is published natively and run on Windows, Linux and macOS on every pull request, scheduling and firing over a real SQLite store and binding a whole scheduler out of an IConfiguration. Quartz produces no IL3050 at all. Quartz.Jobs, Quartz.Plugins, Quartz.HttpClient, Quartz.AspNetCore, Quartz.Extensions.Redis and Quartz.Plugins.TimeZoneConverter are marked trimmable and carry their own baselines; Quartz.Serialization.Newtonsoft and Quartz.Dashboard say the opposite, deliberately.

What is left is 23 recorded IL2xxx entries over 14 types, in src/Quartz/TrimAnalysisBaseline.cs (18 over 11 types), src/Quartz/ILLink.Suppressions.xml (one more that only ILLink sees), and the four package baselines. They are three shapes, and none of them is a bug:

  1. A string names a type — flat quartz.* keys, JOB_CLASS_NAME, a driver name, a plugin or listener type name, a job named in quartz_jobs.json. Every public API that takes such a string already says [RequiresUnreferencedCode]. What is recorded is Quartz reading its own configuration and its own tables, on paths reachable from AddQuartz or across an interface Quartz does not own both sides of.
  2. A value coerced onto a property the compiler cannot seeQuartz.Util.ValueConverter. The target arrives as PropertyInfo.PropertyType, which the framework does not annotate and could not, so there is no chain to build.
  3. Duck-typed provider errorsTransientErrorDetector reads SqlException.Number and SqliteException.SqliteErrorCode off types Quartz deliberately does not reference.

Closing any of these means deleting a feature, not writing an annotation. This issue stays open as the standing record the two baseline files point at, and as the rule they enforce: a warning against a type not listed there means new reflection — fix it rather than adding a line. TrimAnalysisBaseline.cs gives the order to try the fixes in.

Open, and genuinely open:

  • A job-data value of an application's own type in a reflection-off publish. Answered by SystemTextJsonSerializerRegistry.AddTypeInfoResolver; the open registry is deliberately not source-generated. Documented in docs/documentation/quartz-4.x/how-tos/trimming-and-native-aot.md. The remaining work is ergonomics, not correctness.
  • Quartz.Serialization.Newtonsoft and Quartz.Dashboard will not become trimmable. Json.NET's contract is reflection and Blazor Server binds components by name. Recorded, not tracked.