Override Changes Nullability Contract
ID |
java.method_overrides_change_contracts |
Severity |
high |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Code Smell |
Language |
Java |
Tags |
best-practice, reliability |
Description
Reports an overriding method that tightens a @Nullable parameter to @Nonnull, or loosens a @Nonnull return value to @Nullable, relative to the superclass method it overrides.
Rationale
Code holding a reference typed as the superclass relies on the nullability contract that class advertises. If an override secretly requires a non-null argument where the superclass accepted null, or starts returning null where the superclass promised it never would, a caller that only knows about the superclass contract can be handed a subclass instance and break at runtime — exactly the kind of substitution failure the Liskov substitution principle warns about.
class Cache {
void put(@Nullable String value) {
// accepts null
}
}
class StrictCache extends Cache {
@Override
void put(@Nonnull String value) { // Bad -- narrower than what Cache promises
// ...
}
}
Remediation
Keep the override’s contract at least as permissive as the superclass: accept whatever the superclass accepts, and promise to return at least what the superclass promises.
class Cache {
void put(@Nullable String value) {
// accepts null
}
}
class StrictCache extends Cache {
@Override
void put(@Nullable String value) { // Good -- honors the inherited contract
if (value != null) {
// ...
}
}
}