call_loop(), mark_failed(), and call_dump_config() Are No Longer Virtual
Component::call_loop(), Component::mark_failed(), and Component::call_dump_config() are no longer virtual methods. External components that override any of these methods must remove them entirely and move logic to loop() or dump_config(). This saves 800+ bytes of flash globally from vtable elimination.
This is a breaking change for external components in ESPHome 2026.3.0 and later.
Background
Section titled “Background”PR #14083: Devirtualize call_loop() and mark_failed() in Component PR #14355: Devirtualize call_dump_config()
These methods were virtual since the original 2019 codebase, designed to let intermediate base classes inject behavior between the framework and the user’s loop() / dump_config() without requiring base class calls. The components that historically used these overrides have all been refactored:
MQTTComponent::call_loop()— removed in #13356 (Jan 2026) when MQTT was restructuredESP32BLE::mark_failed()— removed in #4173 (Jan 2023) when BLE was refactored to use event handlersMQTTComponent::call_dump_config()— removed in #14355; the override incorrectly skippeddump_config()for internal entities
After those architectural improvements, zero overrides remained. Making these methods non-virtual eliminates one vtable slot per method from every Component-derived class (~100+ vtables in a typical build), saving 800+ bytes of flash.
What’s Changing
Section titled “What’s Changing”// Before (virtual)class Component { public: virtual void call_loop(); virtual void mark_failed(); virtual void call_dump_config();};
// After (non-virtual)class Component { public: void call_loop(); void mark_failed(); void call_dump_config();};Any class that declares call_loop() override, mark_failed() override, or call_dump_config() override will fail to compile.
Who This Affects
Section titled “Who This Affects”External components that override call_loop(), mark_failed(), or call_dump_config(). No known external components currently override any of these methods — they were virtual with zero overrides in the wild.
Standard YAML configurations are not affected.
Migration Guide
Section titled “Migration Guide”Remove overrides and move logic to loop()/dump_config()
Section titled “Remove overrides and move logic to loop()/dump_config()”Simply removing the override keyword is not enough — a same-named non-virtual method in a derived class will never be called by the framework (calls through Component* always dispatch to Component::call_loop()). You must remove the method entirely and move logic to the appropriate virtual hook.
// Before — fails to compileclass MyComponent : public Component { public: void call_loop() override { // Custom per-loop logic } void call_dump_config() override { // Custom config output } void mark_failed() override { // Custom failure handling }};
// After — use loop(), dump_config(), and is_failed() insteadclass MyComponent : public Component { public: void loop() override { // Former call_loop() logic goes here if (this->is_failed()) { // Former mark_failed() follow-up handling goes here return; } } void dump_config() override { // Former call_dump_config() logic goes here }};Supporting Multiple ESPHome Versions
Section titled “Supporting Multiple ESPHome Versions”Since override on a non-virtual method is a compile error, you need a version guard:
#if ESPHOME_VERSION_CODE >= VERSION_CODE(2026, 3, 0)// These methods are no longer virtual — do not override#elsevoid call_loop() override { /* ... */ }void mark_failed() override { /* ... */ }void call_dump_config() override { /* ... */ }#endifTimeline
Section titled “Timeline”- ESPHome 2026.3.0 (March 2026):
call_loop(),mark_failed(), andcall_dump_config()are no longer virtual —overridekeyword causes compile error
Finding Code That Needs Updates
Section titled “Finding Code That Needs Updates”# Find call_loop() overridesgrep -rn 'call_loop().*override' your_component/
# Find mark_failed() overridesgrep -rn 'mark_failed().*override' your_component/
# Find call_dump_config() overridesgrep -rn 'call_dump_config().*override' your_component/Questions?
Section titled “Questions?”If you have questions about migrating your external component, please ask in:
- ESPHome Discord - #devs channel
- ESPHome GitHub Discussions
Related Documentation
Section titled “Related Documentation”Copyright © 2026 ESPHome - A project from the Open Home Foundation
Comments
Feel free to leave a comment here to discuss this post with others. You can ask questions, share your experience, or suggest improvements. If you have a question about a specific feature or issue, please consider using the ESPHome Discord. Stick to English and follow ESPHome's code of conduct. These comments exist on a discussion on GitHub, so you can also comment there directly if you prefer.