diff --git a/docs/documentation/features.md b/docs/documentation/features.md
index 8edfec98e5..bcd657eca3 100644
--- a/docs/documentation/features.md
+++ b/docs/documentation/features.md
@@ -160,9 +160,7 @@ larger than the `.observedGeneration` field on status. In order to have this fea
   .
 
 If these conditions are fulfilled and generation awareness not turned off, the observed generation is automatically set
-by the framework after the `reconcile` method is called. There is just one exception, when the reconciler returns
-with `UpdateControl.updateResource`, in this case the status is not updated, just the custom resource - however, this
-update will lead to a new reconciliation. Note that the observed generation is updated also
+by the framework after the `reconcile` method is called. Note that the observed generation is updated also
 when `UpdateControl.noUpdate()` is returned from the reconciler. See this feature working in
 the [WebPage example](https://github.com/java-operator-sdk/java-operator-sdk/blob/b91221bb54af19761a617bf18eef381e8ceb3b4c/sample-operators/webpage/src/main/java/io/javaoperatorsdk/operator/sample/WebPageStatus.java#L5)
 .
@@ -223,6 +221,30 @@ public class DeploymentReconciler
   }
 ```
 
+## Max Interval Between Reconciliations
+
+In case informers are all in place and reconciler is implemented correctly, there is no need for additional triggers. 
+However, it's a [common practice](https://github.com/java-operator-sdk/java-operator-sdk/issues/848#issuecomment-1016419966) 
+to have a failsafe periodic trigger in place,
+just to make sure the resources are reconciled after certain time. This functionality is in place by default, there
+is quite high interval (currently 10 hours) while the reconciliation is triggered. See how to override this using 
+the standard annotation:
+
+```java
+@ControllerConfiguration(finalizerName = NO_FINALIZER,
+        reconciliationMaxInterval = @ReconciliationMaxInterval(
+                interval = 50,
+                timeUnit = TimeUnit.MILLISECONDS))
+```
+
+The event is not propagated in a fixed rate, rather it's scheduled after each reconciliation. So the 
+next reconciliation will after at most within the specified interval after last reconciliation.
+
+This feature can be turned off by setting `reconciliationMaxInterval` to [`Constants.NO_RECONCILIATION_MAX_INTERVAL`](https://github.com/java-operator-sdk/java-operator-sdk/blob/442e7d8718e992a36880e42bd0a5c01affaec9df/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java#L8-L8)
+or any non-positive number.
+
+The automatic retries are not affected by this feature, in case of an error no schedule is set by this feature. 
+
 ## Automatic Retries on Error
 
 When an exception is thrown from a controller, the framework will schedule an automatic retry of the reconciliation. The
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
index b85df49eba..eb247752aa 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java
@@ -1,7 +1,9 @@
 package io.javaoperatorsdk.operator.api.config;
 
 import java.lang.reflect.ParameterizedType;
+import java.time.Duration;
 import java.util.Collections;
+import java.util.Optional;
 import java.util.Set;
 
 import io.fabric8.kubernetes.api.model.HasMetadata;
@@ -114,4 +116,8 @@ default boolean useFinalizer() {
   default ResourceEventFilter<R> getEventFilter() {
     return ResourceEventFilters.passthrough();
   }
+
+  default Optional<Duration> reconciliationMaxInterval() {
+    return Optional.of(Duration.ofHours(10L));
+  }
 }
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
index e8e2ef1162..c018c369f0 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java
@@ -1,5 +1,6 @@
 package io.javaoperatorsdk.operator.api.config;
 
+import java.time.Duration;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -16,6 +17,7 @@ public class ControllerConfigurationOverrider<R extends HasMetadata> {
   private String labelSelector;
   private ResourceEventFilter<R> customResourcePredicate;
   private final ControllerConfiguration<R> original;
+  private Duration reconciliationMaxInterval;
 
   private ControllerConfigurationOverrider(ControllerConfiguration<R> original) {
     finalizer = original.getFinalizer();
@@ -24,7 +26,9 @@ private ControllerConfigurationOverrider(ControllerConfiguration<R> original) {
     retry = original.getRetryConfiguration();
     labelSelector = original.getLabelSelector();
     customResourcePredicate = original.getEventFilter();
+    reconciliationMaxInterval = original.reconciliationMaxInterval().orElse(null);
     this.original = original;
+
   }
 
   public ControllerConfigurationOverrider<R> withFinalizer(String finalizer) {
@@ -74,6 +78,12 @@ public ControllerConfigurationOverrider<R> withCustomResourcePredicate(
     return this;
   }
 
+  public ControllerConfigurationOverrider<R> withReconciliationMaxInterval(
+      Duration reconciliationMaxInterval) {
+    this.reconciliationMaxInterval = reconciliationMaxInterval;
+    return this;
+  }
+
   public ControllerConfiguration<R> build() {
     return new DefaultControllerConfiguration<>(
         original.getAssociatedReconcilerClassName(),
@@ -86,6 +96,7 @@ public ControllerConfiguration<R> build() {
         labelSelector,
         customResourcePredicate,
         original.getResourceClass(),
+        reconciliationMaxInterval,
         original.getConfigurationService());
   }
 
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java
index 860152745b..33ff56899e 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/DefaultControllerConfiguration.java
@@ -1,6 +1,8 @@
 package io.javaoperatorsdk.operator.api.config;
 
+import java.time.Duration;
 import java.util.Collections;
+import java.util.Optional;
 import java.util.Set;
 
 import io.fabric8.kubernetes.api.model.HasMetadata;
@@ -20,6 +22,7 @@ public class DefaultControllerConfiguration<R extends HasMetadata>
   private final String labelSelector;
   private final ResourceEventFilter<R> resourceEventFilter;
   private final Class<R> resourceClass;
+  private final Duration reconciliationMaxInterval;
   private ConfigurationService service;
 
   public DefaultControllerConfiguration(
@@ -33,6 +36,7 @@ public DefaultControllerConfiguration(
       String labelSelector,
       ResourceEventFilter<R> resourceEventFilter,
       Class<R> resourceClass,
+      Duration reconciliationMaxInterval,
       ConfigurationService service) {
     this.associatedControllerClassName = associatedControllerClassName;
     this.name = name;
@@ -41,6 +45,7 @@ public DefaultControllerConfiguration(
     this.generationAware = generationAware;
     this.namespaces =
         namespaces != null ? Collections.unmodifiableSet(namespaces) : Collections.emptySet();
+    this.reconciliationMaxInterval = reconciliationMaxInterval;
     this.watchAllNamespaces = this.namespaces.isEmpty();
     this.retryConfiguration =
         retryConfiguration == null
@@ -122,4 +127,9 @@ public Class<R> getResourceClass() {
   public ResourceEventFilter<R> getEventFilter() {
     return resourceEventFilter;
   }
+
+  @Override
+  public Optional<Duration> reconciliationMaxInterval() {
+    return Optional.ofNullable(reconciliationMaxInterval);
+  }
 }
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java
index 075b4e79a3..85b3a00807 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/Constants.java
@@ -5,6 +5,7 @@ public final class Constants {
   public static final String EMPTY_STRING = "";
   public static final String WATCH_CURRENT_NAMESPACE = "JOSDK_WATCH_CURRENT";
   public static final String NO_FINALIZER = "JOSDK_NO_FINALIZER";
+  public static final long NO_RECONCILIATION_MAX_INTERVAL = -1L;
 
   private Constants() {}
 }
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java
index fad4bc8573..bd8863cad1 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ControllerConfiguration.java
@@ -4,6 +4,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.concurrent.TimeUnit;
 
 import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
 
@@ -56,4 +57,8 @@
    */
   @SuppressWarnings("rawtypes")
   Class<? extends ResourceEventFilter>[] eventFilters() default {};
+
+  ReconciliationMaxInterval reconciliationMaxInterval() default @ReconciliationMaxInterval(
+      interval = 10, timeUnit = TimeUnit.HOURS);
+
 }
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconciliationMaxInterval.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconciliationMaxInterval.java
new file mode 100644
index 0000000000..05459e7123
--- /dev/null
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ReconciliationMaxInterval.java
@@ -0,0 +1,36 @@
+package io.javaoperatorsdk.operator.api.reconciler;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.concurrent.TimeUnit;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE})
+public @interface ReconciliationMaxInterval {
+
+  /**
+   * A max delay between two reconciliations. Having this value larger than zero, will ensure that a
+   * reconciliation is scheduled with a target interval after the last reconciliation. Note that
+   * this not applies for retries, in case of an exception reconciliation is not scheduled. This is
+   * not a fixed rate, in other words a new reconciliation is scheduled after each reconciliation.
+   * <p/>
+   * If an interval is specified by {@link UpdateControl} or {@link DeleteControl}, those take
+   * precedence.
+   * <p/>
+   * This is a fail-safe feature, in the sense that if informers are in place and the reconciler
+   * implementation is correct, this feature can be turned off.
+   * <p/>
+   * Use NO_RECONCILIATION_MAX_INTERVAL in {@link Constants} to turn off this feature.
+   *
+   * @return max delay between reconciliations
+   **/
+  long interval();
+
+  /**
+   * @return time unit for max delay between reconciliations
+   */
+  TimeUnit timeUnit() default TimeUnit.HOURS;
+
+}
diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java
index 52374c5645..02c8f8cbb3 100644
--- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java
+++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcher.java
@@ -245,9 +245,12 @@ private PostExecutionControl<R> createPostExecutionControl(R updatedCustomResour
   private void updatePostExecutionControlWithReschedule(
       PostExecutionControl<R> postExecutionControl,
       BaseControl<?> baseControl) {
-    baseControl.getScheduleDelay().ifPresent(postExecutionControl::withReSchedule);
+    baseControl.getScheduleDelay().ifPresentOrElse(postExecutionControl::withReSchedule,
+        () -> controller.getConfiguration().reconciliationMaxInterval()
+            .ifPresent(m -> postExecutionControl.withReSchedule(m.toMillis())));
   }
 
+
   private PostExecutionControl<R> handleCleanup(R resource, Context context) {
     log.debug(
         "Executing delete for resource: {} with version: {}",
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java
index a1cfa3e82d..19f65a9441 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ControllerManagerTest.java
@@ -60,7 +60,8 @@ private static class TestControllerConfiguration<R extends HasMetadata>
 
     public TestControllerConfiguration(Reconciler<R> controller, Class<R> crClass) {
       super(null, getControllerName(controller),
-          CustomResource.getCRDName(crClass), null, false, null, null, null, null, crClass, null);
+          CustomResource.getCRDName(crClass), null, false, null, null, null, null, crClass,
+          null, null);
       this.controller = controller;
     }
 
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java
index 9da51337d3..8e4be5e6dc 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/ReconciliationDispatcherTest.java
@@ -1,5 +1,6 @@
 package io.javaoperatorsdk.operator.processing.event;
 
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Optional;
 import java.util.concurrent.TimeUnit;
@@ -47,6 +48,7 @@ class ReconciliationDispatcherTest {
 
   private static final String DEFAULT_FINALIZER = "javaoperatorsdk.io/finalizer";
   public static final String ERROR_MESSAGE = "ErrorMessage";
+  public static final long RECONCILIATION_MAX_INTERVAL = 10L;
   private TestCustomResource testCustomResource;
   private ReconciliationDispatcher<TestCustomResource> reconciliationDispatcher;
   private final Reconciler<TestCustomResource> reconciler = mock(Reconciler.class,
@@ -54,6 +56,7 @@ class ReconciliationDispatcherTest {
   private final ConfigurationService configService = mock(ConfigurationService.class);
   private final CustomResourceFacade<TestCustomResource> customResourceFacade =
       mock(ReconciliationDispatcher.CustomResourceFacade.class);
+  private ControllerConfiguration configuration = mock(ControllerConfiguration.class);
 
   @BeforeEach
   void setup() {
@@ -63,17 +66,23 @@ void setup() {
   }
 
   private <R extends HasMetadata> ReconciliationDispatcher<R> init(R customResource,
-      Reconciler<R> reconciler, ControllerConfiguration<R> configuration,
+      Reconciler<R> reconciler, ControllerConfiguration configuration,
       CustomResourceFacade<R> customResourceFacade, boolean useFinalizer) {
+
     configuration = configuration == null ? mock(ControllerConfiguration.class) : configuration;
+    ReconciliationDispatcherTest.this.configuration = configuration;
     final var finalizer = useFinalizer ? DEFAULT_FINALIZER : Constants.NO_FINALIZER;
     when(configuration.getFinalizer()).thenReturn(finalizer);
     when(configuration.useFinalizer()).thenCallRealMethod();
     when(configuration.getName()).thenReturn("EventDispatcherTestController");
     when(configuration.getResourceClass()).thenReturn((Class<R>) customResource.getClass());
     when(configuration.getRetryConfiguration()).thenReturn(RetryConfiguration.DEFAULT);
+    when(configuration.reconciliationMaxInterval())
+        .thenReturn(Optional.of(Duration.ofHours(RECONCILIATION_MAX_INTERVAL)));
+
     when(configuration.getConfigurationService()).thenReturn(configService);
 
+
     /*
      * We need this for mock reconcilers to properly generate the expected UpdateControl: without
      * this, calls such as `when(reconciler.reconcile(eq(testCustomResource),
@@ -429,6 +438,35 @@ void callErrorStatusHandlerEvenOnFirstError() {
         any(), any());
   }
 
+  @Test
+  void schedulesReconciliationIfMaxDelayIsSet() {
+    testCustomResource.addFinalizer(DEFAULT_FINALIZER);
+
+    when(reconciler.reconcile(eq(testCustomResource), any()))
+        .thenReturn(UpdateControl.noUpdate());
+
+    PostExecutionControl control =
+        reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
+
+    assertThat(control.getReScheduleDelay()).isPresent()
+        .hasValue(TimeUnit.HOURS.toMillis(RECONCILIATION_MAX_INTERVAL));
+  }
+
+  @Test
+  void canSkipSchedulingMaxDelayIf() {
+    testCustomResource.addFinalizer(DEFAULT_FINALIZER);
+
+    when(reconciler.reconcile(eq(testCustomResource), any()))
+        .thenReturn(UpdateControl.noUpdate());
+    when(configuration.reconciliationMaxInterval())
+        .thenReturn(Optional.empty());
+
+    PostExecutionControl control =
+        reconciliationDispatcher.handleExecution(executionScopeWithCREvent(testCustomResource));
+
+    assertThat(control.getReScheduleDelay()).isNotPresent();
+  }
+
   private ObservedGenCustomResource createObservedGenCustomResource() {
     ObservedGenCustomResource observedGenCustomResource = new ObservedGenCustomResource();
     observedGenCustomResource.setMetadata(new ObjectMeta());
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java
index 17168876a5..433614fe17 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/ResourceEventFilterTest.java
@@ -171,7 +171,7 @@ public ControllerConfig(String finalizer, boolean generationAware,
           null,
           eventFilter,
           customResourceClass,
-          null);
+          null, null);
     }
   }
 
diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java
index 5cdc85c553..5859115ee2 100644
--- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java
+++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/controller/ControllerResourceEventSourceTest.java
@@ -170,6 +170,7 @@ public TestConfiguration(boolean generationAware) {
           null,
           null,
           TestCustomResource.class,
+          null,
           null);
     }
   }
diff --git a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java
index 5941975a71..3393de7a55 100644
--- a/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java
+++ b/operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/OperatorExtension.java
@@ -112,6 +112,12 @@ public List<Reconciler> getReconcilers() {
         .collect(Collectors.toUnmodifiableList());
   }
 
+  public Reconciler getFirstReconciler() {
+    return operator.getControllers().stream()
+        .map(Controller::getReconciler)
+        .findFirst().orElseThrow();
+  }
+
   @SuppressWarnings({"rawtypes"})
   public <T extends Reconciler> T getControllerOfType(Class<T> type) {
     return operator.getControllers().stream()
diff --git a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java
index 1000cb61c4..8977bf3ff8 100644
--- a/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java
+++ b/operator-framework/src/main/java/io/javaoperatorsdk/operator/config/runtime/AnnotationConfiguration.java
@@ -1,5 +1,7 @@
 package io.javaoperatorsdk.operator.config.runtime;
 
+import java.time.Duration;
+import java.util.Optional;
 import java.util.Set;
 import java.util.function.Function;
 
@@ -108,6 +110,21 @@ public ResourceEventFilter<R> getEventFilter() {
         : ResourceEventFilters.passthrough();
   }
 
+
+
+  @Override
+  public Optional<Duration> reconciliationMaxInterval() {
+    if (annotation.reconciliationMaxInterval() != null) {
+      if (annotation.reconciliationMaxInterval().interval() <= 0) {
+        return Optional.empty();
+      }
+      return Optional.of(Duration.of(annotation.reconciliationMaxInterval().interval(),
+          annotation.reconciliationMaxInterval().timeUnit().toChronoUnit()));
+    } else {
+      return io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxInterval();
+    }
+  }
+
   public static <T> T valueOrDefault(ControllerConfiguration controllerConfiguration,
       Function<ControllerConfiguration, T> mapper,
       T defaultValue) {
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ConcurrencyIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ConcurrencyIT.java
index 458e2d12eb..12cf3ca4d8 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ConcurrencyIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ConcurrencyIT.java
@@ -19,7 +19,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
 
-public class ConcurrencyIT {
+class ConcurrencyIT {
   public static final int NUMBER_OF_RESOURCES_CREATED = 50;
   public static final int NUMBER_OF_RESOURCES_DELETED = 30;
   public static final int NUMBER_OF_RESOURCES_UPDATED = 20;
@@ -34,7 +34,7 @@ public class ConcurrencyIT {
           .build();
 
   @Test
-  public void manyResourcesGetCreatedUpdatedAndDeleted() throws InterruptedException {
+  void manyResourcesGetCreatedUpdatedAndDeleted() throws InterruptedException {
     log.info("Creating {} new resources", NUMBER_OF_RESOURCES_CREATED);
     for (int i = 0; i < NUMBER_OF_RESOURCES_CREATED; i++) {
       TestCustomResource tcr = TestUtils.testCustomResourceWithPrefix(String.valueOf(i));
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java
index b1f783b958..dbd015587e 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ControllerExecutionIT.java
@@ -15,7 +15,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
 
-public class ControllerExecutionIT {
+class ControllerExecutionIT {
   @RegisterExtension
   OperatorExtension operator =
       OperatorExtension.builder()
@@ -24,7 +24,7 @@ public class ControllerExecutionIT {
           .build();
 
   @Test
-  public void configMapGetsCreatedForTestCustomResource() {
+  void configMapGetsCreatedForTestCustomResource() {
     operator.getControllerOfType(TestReconciler.class).setUpdateStatus(true);
 
     TestCustomResource resource = TestUtils.testCustomResource();
@@ -36,7 +36,7 @@ public void configMapGetsCreatedForTestCustomResource() {
   }
 
   @Test
-  public void eventIsSkippedChangedOnMetadataOnlyUpdate() {
+  void eventIsSkippedChangedOnMetadataOnlyUpdate() {
     operator.getControllerOfType(TestReconciler.class).setUpdateStatus(false);
 
     TestCustomResource resource = TestUtils.testCustomResource();
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/CustomResourceFilterIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/CustomResourceFilterIT.java
index 876710ff36..70a3f04777 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/CustomResourceFilterIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/CustomResourceFilterIT.java
@@ -12,7 +12,7 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-public class CustomResourceFilterIT {
+class CustomResourceFilterIT {
 
   @RegisterExtension
   OperatorExtension operator =
@@ -22,7 +22,7 @@ public class CustomResourceFilterIT {
           .build();
 
   @Test
-  public void doesCustomFiltering() throws InterruptedException {
+  void doesCustomFiltering() throws InterruptedException {
     var filtered1 = createTestResource("filtered1", true, false);
     var filtered2 = createTestResource("filtered2", false, true);
     var notFiltered = createTestResource("notfiltered", true, true);
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ErrorStatusHandlerIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ErrorStatusHandlerIT.java
index 4a1b3293e7..fbf54a1ad1 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ErrorStatusHandlerIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ErrorStatusHandlerIT.java
@@ -15,7 +15,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
 
-public class ErrorStatusHandlerIT {
+class ErrorStatusHandlerIT {
 
   public static final int MAX_RETRY_ATTEMPTS = 3;
   ErrorStatusHandlerTestReconciler reconciler = new ErrorStatusHandlerTestReconciler();
@@ -29,7 +29,7 @@ public class ErrorStatusHandlerIT {
           .build();
 
   @Test
-  public void testErrorMessageSetEventually() {
+  void testErrorMessageSetEventually() {
     ErrorStatusHandlerTestCustomResource resource =
         operator.create(ErrorStatusHandlerTestCustomResource.class, createCustomResource());
 
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/EventSourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/EventSourceIT.java
index 033bbf2b8b..c91ebaad43 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/EventSourceIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/EventSourceIT.java
@@ -16,7 +16,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
 
-public class EventSourceIT {
+class EventSourceIT {
   @RegisterExtension
   OperatorExtension operator =
       OperatorExtension.builder()
@@ -25,7 +25,7 @@ public class EventSourceIT {
           .build();
 
   @Test
-  public void receivingPeriodicEvents() {
+  void receivingPeriodicEvents() {
     EventSourceTestCustomResource resource = createTestCustomResource("1");
 
     operator.create(EventSourceTestCustomResource.class, resource);
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerEventSourceIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerEventSourceIT.java
index 4885e7d356..7ff36bd375 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerEventSourceIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerEventSourceIT.java
@@ -18,7 +18,7 @@
 import static org.assertj.core.api.Assertions.fail;
 import static org.awaitility.Awaitility.await;
 
-public class InformerEventSourceIT {
+class InformerEventSourceIT {
 
   public static final String RESOURCE_NAME = "informertestcr";
   public static final String INITIAL_STATUS_MESSAGE = "Initial Status";
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesResourceStatusUpdateIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesResourceStatusUpdateIT.java
index 2c5ea1cba1..a2c7378ff5 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesResourceStatusUpdateIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/KubernetesResourceStatusUpdateIT.java
@@ -19,7 +19,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
 
-public class KubernetesResourceStatusUpdateIT {
+class KubernetesResourceStatusUpdateIT {
 
   @RegisterExtension
   OperatorExtension operator =
@@ -29,7 +29,7 @@ public class KubernetesResourceStatusUpdateIT {
           .build();
 
   @Test
-  public void testReconciliationOfNonCustomResourceAndStatusUpdate() {
+  void testReconciliationOfNonCustomResourceAndStatusUpdate() {
     var deployment = operator.create(Deployment.class, testDeployment());
     await().atMost(120, TimeUnit.SECONDS).untilAsserted(() -> {
       var d = operator.get(Deployment.class, deployment.getMetadata().getName());
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/MaxIntervalIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MaxIntervalIT.java
new file mode 100644
index 0000000000..f284dbf407
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/MaxIntervalIT.java
@@ -0,0 +1,45 @@
+package io.javaoperatorsdk.operator;
+
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.fabric8.kubernetes.api.model.ObjectMeta;
+import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService;
+import io.javaoperatorsdk.operator.junit.OperatorExtension;
+import io.javaoperatorsdk.operator.sample.maxinterval.MaxIntervalTestCustomResource;
+import io.javaoperatorsdk.operator.sample.maxinterval.MaxIntervalTestReconciler;
+
+import static org.awaitility.Awaitility.await;
+
+class MaxIntervalIT {
+
+  @RegisterExtension
+  OperatorExtension operator =
+      OperatorExtension.builder()
+          .withConfigurationService(DefaultConfigurationService.instance())
+          .withReconciler(new MaxIntervalTestReconciler())
+          .build();
+
+  @Test
+  void reconciliationTriggeredBasedOnMaxInterval() {
+    MaxIntervalTestCustomResource cr = createTestResource();
+
+    operator.create(MaxIntervalTestCustomResource.class, cr);
+
+    await()
+        .pollInterval(50, TimeUnit.MILLISECONDS)
+        .atMost(500, TimeUnit.MILLISECONDS)
+        .until(
+            () -> ((MaxIntervalTestReconciler) operator.getFirstReconciler())
+                .getNumberOfExecutions() > 3);
+  }
+
+  private MaxIntervalTestCustomResource createTestResource() {
+    MaxIntervalTestCustomResource cr = new MaxIntervalTestCustomResource();
+    cr.setMetadata(new ObjectMeta());
+    cr.getMetadata().setName("maxintervaltest1");
+    return cr;
+  }
+}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ObservedGenerationHandlingIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ObservedGenerationHandlingIT.java
index 9eac198f35..5f86336a1c 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/ObservedGenerationHandlingIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/ObservedGenerationHandlingIT.java
@@ -14,7 +14,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
 
-public class ObservedGenerationHandlingIT {
+class ObservedGenerationHandlingIT {
   @RegisterExtension
   OperatorExtension operator =
       OperatorExtension.builder()
@@ -23,7 +23,7 @@ public class ObservedGenerationHandlingIT {
           .build();
 
   @Test
-  public void testReconciliationOfNonCustomResourceAndStatusUpdate() {
+  void testReconciliationOfNonCustomResourceAndStatusUpdate() {
     var resource = new ObservedGenerationTestCustomResource();
     resource.setMetadata(new ObjectMeta());
     resource.getMetadata().setName("observed-gen1");
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryIT.java
index 4ab63c7513..c4ab025ac5 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryIT.java
@@ -18,7 +18,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
 
-public class RetryIT {
+class RetryIT {
   public static final int RETRY_INTERVAL = 150;
   public static final int MAX_RETRY_ATTEMPTS = 5;
 
@@ -36,7 +36,7 @@ public class RetryIT {
 
 
   @Test
-  public void retryFailedExecution() {
+  void retryFailedExecution() {
     RetryTestCustomResource resource = createTestCustomResource("1");
 
     operator.create(RetryTestCustomResource.class, resource);
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryMaxAttemptIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryMaxAttemptIT.java
index e855b016fd..5ebb05eb0c 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryMaxAttemptIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/RetryMaxAttemptIT.java
@@ -12,7 +12,7 @@
 import static io.javaoperatorsdk.operator.RetryIT.createTestCustomResource;
 import static org.assertj.core.api.Assertions.assertThat;
 
-public class RetryMaxAttemptIT {
+class RetryMaxAttemptIT {
 
   public static final int MAX_RETRY_ATTEMPTS = 3;
   public static final int RETRY_INTERVAL = 100;
@@ -31,7 +31,7 @@ public class RetryMaxAttemptIT {
 
 
   @Test
-  public void retryFailedExecution() throws InterruptedException {
+  void retryFailedExecution() throws InterruptedException {
     RetryTestCustomResource resource = createTestCustomResource("max-retry");
 
     operator.create(RetryTestCustomResource.class, resource);
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/SubResourceUpdateIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/SubResourceUpdateIT.java
index 5998502f32..75597643bf 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/SubResourceUpdateIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/SubResourceUpdateIT.java
@@ -18,7 +18,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
 
-public class SubResourceUpdateIT {
+class SubResourceUpdateIT {
 
   @RegisterExtension
   OperatorExtension operator =
@@ -28,7 +28,7 @@ public class SubResourceUpdateIT {
           .build();
 
   @Test
-  public void updatesSubResourceStatus() {
+  void updatesSubResourceStatus() {
     SubResourceTestCustomResource resource = createTestCustomResource("1");
     operator.create(SubResourceTestCustomResource.class, resource);
 
@@ -41,7 +41,7 @@ public void updatesSubResourceStatus() {
   }
 
   @Test
-  public void updatesSubResourceStatusNoFinalizer() {
+  void updatesSubResourceStatusNoFinalizer() {
     SubResourceTestCustomResource resource = createTestCustomResource("1");
     resource.getMetadata().setFinalizers(Collections.emptyList());
 
@@ -57,7 +57,7 @@ public void updatesSubResourceStatusNoFinalizer() {
 
   /** Note that we check on controller impl if there is finalizer on execution. */
   @Test
-  public void ifNoFinalizerPresentFirstAddsTheFinalizerThenExecutesControllerAgain() {
+  void ifNoFinalizerPresentFirstAddsTheFinalizerThenExecutesControllerAgain() {
     SubResourceTestCustomResource resource = createTestCustomResource("1");
     resource.getMetadata().getFinalizers().clear();
     operator.create(SubResourceTestCustomResource.class, resource);
@@ -77,7 +77,7 @@ public void ifNoFinalizerPresentFirstAddsTheFinalizerThenExecutesControllerAgain
    * fail since its resource version is outdated already.
    */
   @Test
-  public void updateCustomResourceAfterSubResourceChange() {
+  void updateCustomResourceAfterSubResourceChange() {
     SubResourceTestCustomResource resource = createTestCustomResource("1");
     operator.create(SubResourceTestCustomResource.class, resource);
 
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/UpdatingResAndSubResIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/UpdatingResAndSubResIT.java
index 2a479d47df..6bad954400 100644
--- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/UpdatingResAndSubResIT.java
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/UpdatingResAndSubResIT.java
@@ -17,7 +17,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Awaitility.await;
 
-public class UpdatingResAndSubResIT {
+class UpdatingResAndSubResIT {
   @RegisterExtension
   OperatorExtension operator =
       OperatorExtension.builder()
@@ -26,7 +26,7 @@ public class UpdatingResAndSubResIT {
           .build();
 
   @Test
-  public void updatesSubResourceStatus() {
+  void updatesSubResourceStatus() {
     DoubleUpdateTestCustomResource resource = createTestCustomResource("1");
     operator.create(DoubleUpdateTestCustomResource.class, resource);
 
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResource.java
new file mode 100644
index 0000000000..1c6cf81453
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResource.java
@@ -0,0 +1,17 @@
+package io.javaoperatorsdk.operator.sample.maxinterval;
+
+import io.fabric8.kubernetes.api.model.Namespaced;
+import io.fabric8.kubernetes.client.CustomResource;
+import io.fabric8.kubernetes.model.annotation.Group;
+import io.fabric8.kubernetes.model.annotation.Kind;
+import io.fabric8.kubernetes.model.annotation.ShortNames;
+import io.fabric8.kubernetes.model.annotation.Version;
+
+@Group("sample.javaoperatorsdk")
+@Version("v1")
+@Kind("MaxIntervalTestCustomResource")
+@ShortNames("mit")
+public class MaxIntervalTestCustomResource
+    extends CustomResource<Void, MaxIntervalTestCustomResourceStatus>
+    implements Namespaced {
+}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResourceStatus.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResourceStatus.java
new file mode 100644
index 0000000000..5c403febfb
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestCustomResourceStatus.java
@@ -0,0 +1,5 @@
+package io.javaoperatorsdk.operator.sample.maxinterval;
+
+public class MaxIntervalTestCustomResourceStatus {
+
+}
diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestReconciler.java
new file mode 100644
index 0000000000..a5343c27a4
--- /dev/null
+++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/maxinterval/MaxIntervalTestReconciler.java
@@ -0,0 +1,30 @@
+package io.javaoperatorsdk.operator.sample.maxinterval;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import io.javaoperatorsdk.operator.api.reconciler.*;
+import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider;
+
+import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_FINALIZER;
+
+@ControllerConfiguration(finalizerName = NO_FINALIZER,
+    reconciliationMaxInterval = @ReconciliationMaxInterval(interval = 50,
+        timeUnit = TimeUnit.MILLISECONDS))
+public class MaxIntervalTestReconciler
+    implements Reconciler<MaxIntervalTestCustomResource>, TestExecutionInfoProvider {
+
+  private final AtomicInteger numberOfExecutions = new AtomicInteger(0);
+
+  @Override
+  public UpdateControl<MaxIntervalTestCustomResource> reconcile(
+      MaxIntervalTestCustomResource resource, Context context) {
+    numberOfExecutions.addAndGet(1);
+    return UpdateControl.noUpdate();
+  }
+
+  public int getNumberOfExecutions() {
+    return numberOfExecutions.get();
+  }
+
+}