Rule. When adding new methods, callbacks, or listener events to existing public Java interfaces, provide default implementations (such as an empty body {} for void observer/callback methods, or a sensible fallback return value) rather than declaring them as abstract methods without bodies. Additionally, when providing default implementations for interface methods that represent optional features or hardware-specific capabilities, do not add a TODO to remove the default body or make the method abstract/pure virtual, as optional capabilities are intended to have permanent fallbacks rather than temporary migration shims.
Why. Adding abstract methods to existing public Java interfaces breaks source and binary compatibility for external implementers and downstream applications during SDK updates. Providing default implementations allows new methods and callbacks to land safely without forcing immediate subclass modifications. Furthermore, default methods for optional capabilities serve as permanent fallbacks for implementations lacking the feature; adding removal TODOs on optional APIs creates false migration churn by suggesting that all subclasses must eventually implement the method.
Bad:
public interface AudioDeviceObserver { // Bad: Adding abstract method to existing public interface breaks downstream implementers. @CalledByNative("AudioDeviceObserver") void onRemoveTrack(RtpReceiver receiver); // Bad: Optional capability default is marked with a removal TODO. /** TODO(b/279738239): Remove default implementation. */ default boolean setNoiseSuppressorEnabled(boolean enabled) { return false; } }
Good:
public interface AudioDeviceObserver { // Good: Default empty body preserves backward compatibility for existing implementers. @CalledByNative("AudioDeviceObserver") default void onRemoveTrack(RtpReceiver receiver) {} // Good: Permanent default fallback for optional capability without a removal TODO. default boolean setNoiseSuppressorEnabled(boolean enabled) { return false; } }