Long time ago, I’ve seen a pull request for a price problem. The problem was a NullPointerException on an partially configured item with no price. The solution of the developer was a null check.
public BigDecimal computePrice(Product product) { // check for user's promotion ... // check for input discount codes ... if (product.getPrice() != null) { return product.getPrice() .subtract(userPromotions) .subtract(discountAmount); } return BigDecimal.ZERO;}
The thing is: if the product is partially configured, with no price, it’s free. I don’t think the stakeholders will appreciate that. A solution should be throwing an exception if the product is not configured.
But there are alternatives.
Don’t fear throwing exceptions
The first solution should be throwing an exception. Trying to compute the price on a partially configured product can’t be allowed. If it can’t be allowed, it’s a error of the user asking the price, so stop the program excecution with an exception and return the error to the user.
Another point, is waiting to the end of the method to check this condition. All those checks must be done on top of the method. If there is no price set, don’t check for the user’s advantages or the input discount codes. Throw the exception as soon as possible.
public BigDecimal computePrice(Product product) { if (product.getPrice() == null) { throw new AppException("Product " + product.getId() + " has no price"); } // check for user's promotion ... // check for input discount codes ... return product.getPrice() .subtract(userPromotions) .subtract(discountAmount);}
I always have a validation step on top of each method (or controller). This way, all what happens below is safe.
Stop Patching Nulls and Start Adding Constraints
Another way to solve this situation is answering the question in this way: can the product be partially configured? Isn’t the price one of the first things to set when creating a product.
I can have a DRAFT status on the product; so, in the validation step, check for the status not being DRAFT, otherwise throw an exception.
But if I don’t allow DRAFT products, nor partially configured products, I must not allow to save a product without a price.
public record Product( @NotBlank String title, @NotNull BigDecimal price, @NotBlank String description, ...) {}
Now on the controller, I can use the @Valid annotation to ensure all the constraints of the record are satisfied. Otherwise, an error is already thrown by Spring. I don’t even need to handle the validation by myself.
But I can go further and add a validate method in my service to check more subtle constraints like orthography with an LLM, pictures have the correct size…
Going even further in the validation, I can add the constraints in the database.
ALTER TABLE products ALTER COLUMN price SET NOT NULL;
Why, if the controller already handles this check?
Because, I know and you know, there will be some SQL queries that won’t go through the controller. I know and you know this is not good, but there are always special cases.
Data Integrity as a Debugging Strategy
When a bug report arrives, the first instinct is to step through the code. However, I have found that most “impossible” bugs are simply valid code reacting to invalid data.
Before you refactor a function that is behaving strangely, audit the state of the objects it consumes. You will often find that a field was left null, a string was empty, or a status transition occurred that should have been impossible.
- Audit your “Impossible” States: Use
UNIQUEconstraints andFOREIGN KEYchecks to ensure relationships are real. - Use Enums, Not Strings: Prevent “typo-driven development” by restricting status fields to defined types.
- Fail Fast: If data is wrong, throw an exception immediately. Silently continuing with bad data only makes the eventual crash harder to trace.
Actionable Takeaways for System Reliability
The source of the problem is rarely the logic; it is the environment in which that logic operates. To build more resilient systems, shift your focus from code-level patches to data-level enforcement.
- Check your schema: Every column that can be
NOT NULLshould be. - Move validation to the edge: Use DTOs and validation frameworks to reject bad data before it hits your services.
- Stop providing defaults for bad input: Force the caller (or the user) to provide the correct information.
- Trust, but verify: Once data passes the “gate,” treat it as valid. If it isn’t, your gate is broken, not your service.
Your code is only as stable as the data you feed it. Clean code is secondary to clean state.


Leave a comment