{"id":655513,"date":"2025-11-04T14:39:13","date_gmt":"2025-11-04T13:39:13","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=dotnet&#038;p=655513"},"modified":"2025-11-04T14:39:15","modified_gmt":"2025-11-04T13:39:15","slug":"maarten-balliauws-guide-to-csharp-nullable-reference-types","status":"publish","type":"dotnet","link":"https:\/\/blog.jetbrains.com\/en\/dotnet\/2025\/11\/04\/maarten-balliauws-guide-to-csharp-nullable-reference-types","title":{"rendered":"Taming the Billion Dollar Mistake: Maarten Balliauw&#8217;s Guide to C# Nullable Reference Types"},"content":{"rendered":"\n<p>At the most recent <a href=\"http:\/\/jetbrains.net\" target=\"_blank\" rel=\"noopener\">JetBrains.NET Days Online 2025 event<\/a>, Maarten Balliauw delivered a comprehensive session on migrating existing codebases to use C# nullable reference types, a feature that&#8217;s been available since C# 8 but remains underutilized in many legacy projects. Through practical examples and migration strategies, Maarten demonstrated how to bring null safety to your existing code without drowning in thousands of warnings.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Watch the full presentation<\/h2>\n\n\n\n<iframe loading=\"lazy\" width=\"800\" height=\"450\" src=\"https:\/\/www.youtube.com\/embed\/zZniCOWy-4w?si=D2RwTLKOlwjjGfmN\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe>\n\n\n\n<p><em>#nullable enable \u2013 Unlock the Power of C# Nullability \u2014 Maarten Balliauw<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">It&#8217;s not for runtime safety<\/h2>\n\n\n\n<p>Maarten opens with a crucial clarification that sets realistic expectations for the entire migration journey: &#8220;There is no runtime safety for nullable reference types. It is design time and compile time that help to check whether something can be null or not.&#8221; This isn&#8217;t a silver bullet that will eliminate all null reference exceptions \u2013 it&#8217;s a tool to help developers make better decisions while writing code.<\/p>\n\n\n\n<p>The feature essentially flips the traditional C# paradigm. As Maarten explains, &#8220;The idea is that when you&#8217;re writing code, every single type that you have is going to be non-nullable by default. And there is a syntax to annotate a reference type as being nullable.&#8221; This inversion makes nullability explicit rather than implicit, forcing developers to think intentionally about null handling.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Boy Scout Rule for migration<\/h2>\n\n\n\n<p>When it comes to migrating existing codebases, Maarten advocates for a pragmatic approach. For smaller projects, you might enable nullability project-wide and plow through the warnings. But for larger codebases, he recommends the Boy Scout Rule: <\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>&#8220;Keep it disabled at the project level. And whenever you touch a piece of code, you enable it file by file and just work your way through until the entire project is converted.&#8221;<\/p>\n<\/blockquote>\n\n\n\n<p>The key insight for where to begin? &#8220;Start at the center and work outwards&#8221; (<a href=\"https:\/\/youtu.be\/zZniCOWy-4w?si=P07EsJS0H-uhy9pt&amp;t=1797\" target=\"_blank\" rel=\"noopener\">29:59<\/a>). Maarten suggests starting with classes that have few dependencies but are used extensively throughout the codebase, typically data transfer objects and domain models. A small change in these central classes can eliminate warnings across your entire codebase.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The temptation of the dammit operator<\/h2>\n\n\n\n<p>Throughout the presentation, Maarten repeatedly warns against one particular anti-pattern: the null-forgiving operator (!), also known as the &#8220;dammit operator&#8221;. His stance is unequivocal: &#8220;The null forgiving operator is an anti-pattern.&#8221;<\/p>\n\n\n\n<p>Why such strong language? &#8220;You&#8217;re lying to the compiler. You&#8217;re lying to yourself when you are working in this codebase a couple of months after you first wrote the code.&#8221; While the operator can be useful during migration to temporarily suppress warnings, Maarten emphasizes that production code should never ship with these suppressions.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Embracing null, not fighting it<\/h2>\n\n\n\n<p>In a refreshing perspective shift, Maarten reminds developers: &#8220;Don&#8217;t be afraid of null. Nullable reference types will help you get more confidence in compiler and IDE flow analysis. It&#8217;s impossible to get rid of all of the null usages in code&#8221; (<a href=\"https:\/\/youtu.be\/zZniCOWy-4w?si=PgDox5P5Fy0ymO9x&amp;t=2243\" target=\"_blank\" rel=\"noopener\">37:23<\/a>). The goal isn&#8217;t to eliminate null entirely, it&#8217;s to build a safety net for dealing with it effectively.<\/p>\n\n\n\n<p>This philosophy extends to library authors, who need to consider consumers using different languages. Since nullable reference types are C# only, libraries should still include runtime null checks for robustness, especially when the API might be consumed by F# or VB.NET code that won&#8217;t benefit from the nullable annotations.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Entity Framework&#8217;s surprise<\/h2>\n\n\n\n<p>One of the most valuable warnings in the presentation concerns Entity Framework Core, which has a potentially breaking behavior: &#8220;The Entity Framework Core team, unfortunately, did look at this, and they are actually updating your database schema based on nullable reference types.&#8221;<\/p>\n\n\n\n<p>This means that enabling nullable reference types can inadvertently trigger database schema changes, something that might slip past developers but certainly won&#8217;t escape the notice of your DBA. It&#8217;s a &#8220;gotcha&#8221; that Maarten admits makes sense from Entity Framework&#8217;s perspective, but one that can be an unwelcome surprise during migration.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Practical tools for the journey<\/h2>\n\n\n\n<p>Maarten highlights several tools that make migration more manageable (<a href=\"https:\/\/youtu.be\/zZniCOWy-4w?si=4DfDEnWx6rpCuk7e&amp;t=2308\" target=\"_blank\" rel=\"noopener\">38:28<\/a>). The value tracking feature in Microsoft Visual Studio and Rider helps trace where properties are set, making it easier to determine if something can actually be null. The automatic migration tools can get you quite far along the way by running flow analysis and making educated guesses about nullability.<\/p>\n\n\n\n<p>For JSON serialization scenarios, Maarten presents multiple strategies, with his personal favorite being the use of the required keyword combined with proper validation, rather than littering code with null-forgiving operators or losing type safety by making everything nullable.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The takeaway<\/h2>\n\n\n\n<p>Maarten&#8217;s session demystifies the migration to nullable reference types, presenting it not as an all-or-nothing proposition but as a gradual improvement process. His closing summary captures the essence perfectly: &#8220;Nullable reference types give you design time and compile time safety in .NET, not at runtime.&#8221;<\/p>\n\n\n\n<p>This session serves as both a technical guide and a philosophical framework for approaching nullability. It&#8217;s not about achieving perfection or eliminating all nulls \u2013 it&#8217;s about making your codebase more predictable, maintainable, and explicit about its null-handling intentions.<\/p>\n\n\n\n<p>For teams sitting on legacy codebases wondering if the migration effort is worth it, the essence of Maarten\u2019s argument is that the investment in nullable reference types pays dividends in code clarity, reduced bugs, and better communication of intent between team members, including your future self.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>Follow Maarten Balliauw&#8217;s work on nullable reference types and other C# topics on<a href=\"https:\/\/blog.maartenballiauw.be\/\" target=\"_blank\" rel=\"noopener\"> his blog<\/a>, <a href=\"https:\/\/twitter.com\/maartenballiauw\/\" target=\"_blank\" rel=\"noopener\">X<\/a>, and <a href=\"https:\/\/bsky.app\/profile\/maartenballiauw.be\" target=\"_blank\" rel=\"noopener\">Bluesky<\/a>.<\/p>\n\n\n\n<p>Watch the full recording of this year\u2019s online event on YouTube:<\/p>\n\n\n\n<p>\u25b6\ufe0f <a href=\"https:\/\/www.youtube.com\/watch?v=Srsnb7JNXiw&amp;t=6s\" target=\"_blank\" rel=\"noopener\">Day 1: Aspire, Clean Architecture, C# Nullability, Messaging, Uno, Blazor TDD.<\/a><\/p>\n\n\n\n<p>\u25b6\ufe0f <a href=\"https:\/\/www.youtube.com\/watch?v=Aq1xHunHNuA\" target=\"_blank\" rel=\"noopener\">Day 2: Event-Driven Systems &amp; GenAI Qith Aspire, F#, dotMemory, and More.<\/a><\/p>\n\n\n\n<p>You may also find these interesting:<\/p>\n\n\n\n<p><a href=\"https:\/\/mattjameschampion.com\/2025\/06\/16\/the-null-forgiving-operator-in-c-is-a-code-smell\/\" target=\"_blank\" rel=\"noopener\"><em>The Null-Forgiving Operator in C# Is a Code Smell(!)<\/em><\/a>&nbsp; by Matthew Champion.<\/p>\n\n\n\n<p><a href=\"https:\/\/blog.maartenballiauw.be\/post\/2022\/04\/11\/nullable-reference-types-in-csharp-migrating-to-nullable-reference-types-part-1.html\" target=\"_blank\" rel=\"noopener\"><em>Nullable reference types in C# &#8211; Migrating to nullable reference types<\/em><\/a> by Maarten Balliauw.&nbsp;<\/p>\n\n\n\n<p><a href=\"https:\/\/www.damirscorner.com\/blog\/posts\/20220812-NullableReferenceTypesAndNullabilityInEfCore.html\" target=\"_blank\" rel=\"noopener\"><em>Nullable reference types and nullability in EF Core<\/em><\/a> by Damir Arh.<\/p>\n\n\n\n<p><a href=\"https:\/\/www.jetbrains.com\/guide\/dotnet\/links\/null-void-everything-about-nothing-in-net\/\" target=\"_blank\" rel=\"noopener\"><em>Null &amp; Void \u2013 Everything About Nothing in .NET<\/em><\/a> \u2013 a presentation given by Stefan P\u00f6lz at .NET Days Online 2021.&nbsp;<\/p>\n","protected":false},"author":1337,"featured_media":655516,"comment_status":"closed","ping_status":"closed","template":"","categories":[4992,3990,4140],"tags":[211,6699,46,1978,6183],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/dotnet\/655513"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/dotnet"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/types\/dotnet"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/users\/1337"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/comments?post=655513"}],"version-history":[{"count":7,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/dotnet\/655513\/revisions"}],"predecessor-version":[{"id":655531,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/dotnet\/655513\/revisions\/655531"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/media\/655516"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/media?parent=655513"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/categories?post=655513"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/tags?post=655513"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/en\/wp-json\/wp\/v2\/cross-post-tag?post=655513"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}