Null Is Not Empty and Not Zero

I remember sitting in a freezing data center in 2004, staring at a flickering terminal while a legacy payment gateway choked on a single unexpected empty string. It wasn’t a massive architectural failure or a sophisticated hack; it was just a field that someone, somewhere, had decided could be optional without telling anyone. Most tutorials show you the clean, happy path where every data point is perfectly typed and validated, but they never teach you how to handle nullable columns when they actually start poisoning your business logic at 3:00 AM. You think your service is robust until a null value hits a calculation engine and suddenly your entire production pipeline is screaming at you for a reason you can’t immediately trace.

I’m not here to give you a lecture on theoretical database normalization or academic best practices that fall apart the moment you touch real-world code. Instead, I want to show you the scars I’ve earned from twenty years of maintaining messy, imperfect systems. I will show you the specific, ugly ways these columns break your code and, more importantly, how to build the defensive layers necessary to keep them from crashing your service. This is about what happens when the data is not what you expected.

Table of Contents

The Difference Between Null and Empty String Is Killing Your Logic

The Difference Between Null and Empty String Is Killing Your Logic

The problem is that most developers treat `NULL` and an empty string (`”`) as interchangeable placeholders for “nothing.” They aren’t. In my experience, this semantic confusion is where the most expensive bugs hide. An empty string is a value—it’s a piece of data that happens to have zero length. `NULL`, however, is the absence of a value. It represents an unknown state. When you conflate the two, you aren’t just making a stylistic choice; you are actively undermining your database schema design best practices.

I’ve spent many late nights debugging queries where a `WHERE` clause returned nothing, not because the data wasn’t there, but because the logic failed to account for the void. If you try handling NULL in WHERE clauses using standard equality operators like `= ”`, you will miss every single null row. The database won’t complain; it will just silently omit the data, leaving your application in a state of truth that doesn’t actually exist. You think you’re filtering for empty fields, but you’re actually just creating a logical blind spot that will eventually trigger a production incident.

Why Your Not Null Constraint Implementation Is Your Best Defense

Why Your Not Null Constraint Implementation Is Your Best Defense

I’ve spent enough time staring at broken payment reconciliations to know that relying on application-level checks is a losing game. You can write all the validation logic you want in your service layer, but if your database allows a hole to exist, someone—or some rogue migration—will eventually find it. This is why I treat NOT NULL constraint implementation as my first line of defense rather than an afterthought. It isn’t just about data integrity; it’s about creating a hard boundary that forces the error to happen now, at the moment of insertion, instead of three months later when a reporting script fails because it hit an unexpected null.

When you enforce these constraints at the schema level, you aren’t just being pedantic; you are simplifying every single query that follows. It eliminates the need to constantly wrap every single column in a SQL COALESCE function usage just to prevent a calculation from returning garbage. If the schema guarantees the data exists, your logic becomes predictable. You stop writing defensive code for scenarios that should have been physically impossible by design.

Five ways to stop your database from lying to you

  • Stop treating NULL as a value. It isn’t a value; it’s a lack of information. If you find yourself writing `if (value == null)` in every single service layer, your schema is failing you. Decide early if a field is truly optional or if it just needs a sensible default.
  • Use COALESCE in your SQL, but don’t abuse it. It’s tempting to wrap every nullable column in a `COALESCE(col, 0)` to keep the application layer happy, but that masks the fact that you’re losing data integrity. You end up treating “unknown” exactly like “zero,” and in payment systems, that’s a recipe for a reconciliation nightmare.
  • Tighten your application-level validation to match your constraints. There is nothing more frustrating than a backend service that thinks a field is optional, only to have the database reject the write with a `NotNullViolation` because the schema was stricter than the code. Your DTOs and your SQL should be in total agreement.
  • Be wary of Three-Valued Logic in your queries. Most developers forget that `WHERE column != ‘active’` will completely ignore rows where `column` is NULL. I’ve spent far too many late nights debugging why a report was “missing” data, only to realize the NULL rows weren’t actually missing—they were just invisible to the filter.
  • Document the “Why” behind the NULL. If a column is nullable, I need to know if it’s because the data is truly unknown, because it’s irrelevant for certain transaction types, or because the legacy system we’re integrating with is a mess. A nullable column without a comment in the schema is just a ticking time bomb for the next developer.

The Hard Truths

Stop treating NULL as a “missing value” and start treating it as a state; if your business logic doesn’t explicitly account for that state, you aren’t writing code, you’re writing a bug report.

Constraints are not suggestions for the database; they are your last line of defense when the application layer inevitably fails to validate a payload correctly.

If you find yourself writing `if (value != null && value != “”)` more than once, your schema is broken and you’re just delaying the inevitable crash.

## The Cost of Ambiguity

A nullable column is a debt you haven’t realized you’ve taken out yet; it’s fine during the local dev sprint, but eventually, the interest comes due in the form of a production outage where nobody can figure out if the data is missing, broken, or just hasn’t arrived yet.

Pernille Askeland

The Cost of Being Careful

The Cost of Being Careful with NULLs.

At the end of the day, managing nullable columns isn’t about following a style guide; it’s about preventing the kind of midnight pager alerts that make you question your career choices. You have to stop treating `NULL` as a convenient way to avoid defining your data model and start treating it as a contractual obligation. If you can distinguish between an empty string and a missing value, and if you actually enforce your `NOT NULL` constraints at the database level instead of just hoping your application logic catches everything, you’ve already won half the battle. Don’t let your schema be a collection of “maybe” values. Make your data explicit, or prepare to spend your weekends chasing phantom logic errors through a sea of null pointers.

I’ve spent two decades cleaning up the messes left behind by developers who thought they could “fix it in the code later.” They always do, but by then, the technical debt has already calcified. My advice is simple: write your commit message, design your constraints, and assume the error is coming. When you build with the assumption that the data will eventually be messy, you stop building fragile systems and start building resilient ones. It might feel like extra work now, but the peace of mind you get when a production deployment goes smoothly is worth every extra line of validation.

Frequently Asked Questions

If I add a NOT NULL constraint to a legacy table that already has millions of rows, how do I migrate the existing NULL values without locking the table and taking the system offline?

You don’t just run `ALTER TABLE` and hope for the best. On a table with millions of rows, that’s a suicide mission; the lock will hang your entire application. I do it in stages. First, I write a script to batch-update the existing NULLs to a default value in small chunks—maybe 5,000 rows at a time—to keep the transaction logs manageable. Once the data is clean, I add the constraint.

At what point in the stack should I be handling the nullability—should the database enforce it, or should my backend models be the ones catching it?

The short answer is both, but they serve different masters. The database is your final line of defense; it’s the source of truth that prevents corruption when a rogue script or a direct SQL query bypasses your application logic. But your backend models? They are your early warning system. If you wait until a database constraint violation to catch a null, you’ve already let the error drift too far into your execution flow. Catch it in the model to fail fast; enforce it in the DB to stay safe.

How do I prevent "null leakage" where a nullable column in the database forces every single downstream service or API consumer to implement their own defensive null checks?

Stop letting the database’s indecision bleed into your business logic. The moment a `null` escapes your persistence layer, you’ve failed. You prevent leakage by enforcing a strict “Null-to-Value” translation at the edge of your service. Use Data Transfer Objects (DTOs) or domain models that wrap those nullable fields into meaningful types—like an `Option` pattern or a default empty state. If the downstream service has to ask “is this null?”, your boundary is broken.

About Pernille Askeland

I write the part the tutorial skipped: what it looks like when it breaks.

Scroll to Top