Skip to content

Error handling

An interaction can fail to achieve its intended purpose. Baker categorizes failures in technical failures and functional failures.

Technical failures are characterized by the possibility of being retried and eventually succeeding. Examples of technical failures include timeouts due to an unreliable network, temporary unavailability of an external system, and receiving an unexpected response from an external system. These failures are unexpected and are handled by throwing an exception from the interaction.

Functional failures cannot be resolved by retrying the interaction. Examples of functional failures include cases where there is insufficient stock to ship the order, or there is insufficient credit to perform a transfer. These failures are anticipated and considered as potential outcomes of the interaction. They are handled by returning an event from the interaction.

Failure strategies

Baker offers multiple mitigation strategies for technical failures.

Tip

The examples in this section all set the defaultFailureStrategy on recipe level. The same strategies are also available for the failureStrategy on interaction level.

Block interaction

This is the default failure strategy. When an exception occurs the interaction is blocked. This option is suitable for non-idempotent interactions that cannot be retried.

package examples.java.recipes;

import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;

public class RecipeBlockInteraction {

    public final static Recipe recipe = new Recipe("example")
        .withDefaultFailureStrategy(
            InteractionFailureStrategy.BlockInteraction()
        );
}
package examples.kotlin.recipes

import com.ing.baker.recipe.kotlindsl.ExperimentalDsl
import com.ing.baker.recipe.kotlindsl.recipe

@ExperimentalDsl
object RecipeBlockInteraction {
    val recipe = recipe("example") {
        defaultFailureStrategy = blockInteraction()
    }
}
package examples.scala.recipes

import com.ing.baker.recipe.common.InteractionFailureStrategy
import com.ing.baker.recipe.scaladsl.Recipe

object RecipeBlockInteraction {
  val recipe: Recipe = Recipe("example")
    .withDefaultFailureStrategy(
      InteractionFailureStrategy.BlockInteraction()
    )
}

Fire event

This option is the equivalent of a try-catch in code. When an exception occurs an event is fired. Instead of failing, the process continues.

package examples.java.recipes;

import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;

public class RecipeFireEvent {

    public final static Recipe recipe = new Recipe("example")
        .withDefaultFailureStrategy(
            InteractionFailureStrategy.FireEvent("MyEvent")
        );
}
package examples.kotlin.recipes

import com.ing.baker.recipe.kotlindsl.ExperimentalDsl
import com.ing.baker.recipe.kotlindsl.recipe

@ExperimentalDsl
object RecipeFireEvent {
    val recipe = recipe("example") {
        defaultFailureStrategy = fireEventAfterFailure("MyEvent")
    }
}
package examples.scala.recipes

import com.ing.baker.recipe.common.InteractionFailureStrategy
import com.ing.baker.recipe.scaladsl.Recipe

object RecipeFireEvent {
  val recipe: Recipe = Recipe("example")
    .withDefaultFailureStrategy(
      InteractionFailureStrategy.FireEventAfterFailure(Some("MyEvent"))
    )
}

Retry with incremental back-off

Incremental back-off allows you to configure a retry mechanism that exponentially increases the time between each retry. You retry quickly at first, but slower over time. Retry with incremental back-off keeps retrying until a set deadline or the maximum amount of retries is reached.

Until deadline

package examples.java.recipes;

import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;

import java.time.Duration;

public class RecipeRetryWithBackOffUntilDeadline {

    public final static Recipe recipe = new Recipe("example")
        .withDefaultFailureStrategy(
            new InteractionFailureStrategy.RetryWithIncrementalBackoffBuilder()
                .withInitialDelay(Duration.ofMillis(100))
                .withBackoffFactor(2.0)
                .withMaxTimeBetweenRetries(Duration.ofSeconds(100))
                .withDeadline(Duration.ofHours(24))
                .build()
        );
}
package examples.kotlin.recipes

import com.ing.baker.recipe.kotlindsl.ExperimentalDsl
import com.ing.baker.recipe.kotlindsl.recipe
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

@ExperimentalDsl
object RecipeRetryWithBackOffUntilDeadline {
    val recipe = recipe("example") {
        defaultFailureStrategy = retryWithIncrementalBackoff {
            initialDelay = 100.milliseconds
            backoffFactor = 2.0
            maxTimeBetweenRetries = 10.seconds
            until = deadline(24.hours)
        }
    }
}
package examples.scala.recipes

import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff
import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff.UntilDeadline
import com.ing.baker.recipe.scaladsl.Recipe

import scala.concurrent.duration.DurationInt

object RecipeRetryWithBackOffUntilDeadline {
  val recipe: Recipe = Recipe("example")
    .withDefaultFailureStrategy(
      RetryWithIncrementalBackoff
        .builder()
        .withInitialDelay(100.milliseconds)
        .withBackoffFactor(2.0)
        .withMaxTimeBetweenRetries(Some(10.minutes))
        .withUntil(Some(UntilDeadline(24.hours)))
        .build()
    )
}

Until maximum retries

package examples.java.recipes;

import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;

import java.time.Duration;

public class RecipeRetryWithBackOffUntilMaxRetries {

    public final static Recipe recipe = new Recipe("example")
        .withDefaultFailureStrategy(
            new InteractionFailureStrategy.RetryWithIncrementalBackoffBuilder()
                .withInitialDelay(Duration.ofMillis(100))
                .withBackoffFactor(2.0)
                .withMaxTimeBetweenRetries(Duration.ofSeconds(100))
                .withMaximumRetries(200)
                .build()
        );
}
package examples.kotlin.recipes

import com.ing.baker.recipe.kotlindsl.ExperimentalDsl
import com.ing.baker.recipe.kotlindsl.recipe
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

@ExperimentalDsl
object RecipeRetryWithBackOffUntilMaxRetries {
    val recipe = recipe("example") {
        defaultFailureStrategy = retryWithIncrementalBackoff {
            initialDelay = 100.milliseconds
            backoffFactor = 2.0
            maxTimeBetweenRetries = 10.seconds
            until = maximumRetries(200)
        }
    }
}
package examples.scala.recipes

import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff
import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff.UntilMaximumRetries
import com.ing.baker.recipe.scaladsl.Recipe

import scala.concurrent.duration.DurationInt

object RecipeRetryWithBackOffUntilMaxRetries {
  val recipe: Recipe = Recipe("example")
    .withDefaultFailureStrategy(
      RetryWithIncrementalBackoff
        .builder()
        .withInitialDelay(100.milliseconds)
        .withBackoffFactor(2.0)
        .withMaxTimeBetweenRetries(Some(10.minutes))
        .withUntil(Some(UntilMaximumRetries(200)))
        .build()
    )
}
name meaning
initialDelay The delay before retrying for the first time.
backoffFactor The backoff factor for the delay, defaults to 2
maxTimeBetweenRetries The maximum time between retries.
deadLine The total amount of time spend retrying.
maximumRetries The maximum amount of retries.

Our example results in a retry pattern off:

100 millis -> 200 millis -> 400 millis -> ... -> 100 seconds -> 100 seconds.

Which can be visualized like this:

Visual representation of incremental back-off

Note

Delays do not include the interaction execution time. If the first retry takes 5 seconds (and fails), the second retry will be triggered after:

(100 millis + 5 seconds + 200 millis) = 5.3 seconds

The deadline also does not consider interaction execution time. Keep this in mind when setting the deadline value.

Retry exhaustion

If an interaction keeps failing, the retry is exhausted and the interaction becomes blocked. If you don't want the interaction to block after exhausting all the retries, you can continue the process with a predefined event.

package examples.java.recipes;

import com.ing.baker.recipe.javadsl.InteractionFailureStrategy;
import com.ing.baker.recipe.javadsl.Recipe;

import java.time.Duration;

public class RecipeRetryExhaustedEvent {

    public final static Recipe recipe = new Recipe("example")
        .withDefaultFailureStrategy(
            new InteractionFailureStrategy.RetryWithIncrementalBackoffBuilder()
                .withInitialDelay(Duration.ofMillis(100))
                .withBackoffFactor(2.0)
                .withMaxTimeBetweenRetries(Duration.ofSeconds(100))
                .withDeadline(Duration.ofHours(24))
                .withFireRetryExhaustedEvent("RetriesExhausted")
                .build()
        );
}
package examples.kotlin.recipes

import com.ing.baker.recipe.kotlindsl.ExperimentalDsl
import com.ing.baker.recipe.kotlindsl.recipe
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

@ExperimentalDsl
object RecipeRetryExhaustedEvent {
    val recipe = recipe("example") {
        defaultFailureStrategy = retryWithIncrementalBackoff {
            initialDelay = 100.milliseconds
            backoffFactor = 2.0
            maxTimeBetweenRetries = 10.seconds
            until = deadline(24.hours)
            fireRetryExhaustedEvent = "RetriesExhausted"
        }
    }
}
package examples.scala.recipes

import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff
import com.ing.baker.recipe.common.InteractionFailureStrategy.RetryWithIncrementalBackoff.UntilDeadline
import com.ing.baker.recipe.scaladsl.Recipe

import scala.concurrent.duration.DurationInt

object RecipeRetryExhaustedEvent {
  val recipe: Recipe = Recipe("example")
    .withDefaultFailureStrategy(
      RetryWithIncrementalBackoff
        .builder()
        .withInitialDelay(100.milliseconds)
        .withBackoffFactor(2.0)
        .withMaxTimeBetweenRetries(Some(10.minutes))
        .withUntil(Some(UntilDeadline(24.hours)))
        .withFireRetryExhaustedEvent(Some("RetriesExhausted"))
        .build()
    )
}

Manual intervention

Baker allows you to resolve blocked interactions, and to stop retrying interactions via manual intervention.

Force a retry

This method retries a given interaction a single time. If it succeeds, your process continues. If it fails, the interaction stays blocked.

package examples.java.application;

import com.ing.baker.runtime.javadsl.Baker;

public class ManualRetryInteraction {

    public void retryExample(Baker baker, String recipeInstanceId) {
        baker.retryInteraction(recipeInstanceId, "ShipOrder");
    }
}
package examples.kotlin.application

import com.ing.baker.runtime.kotlindsl.Baker

suspend fun retryExample(baker: Baker, recipeInstanceId: String) {
    baker.retryInteraction(recipeInstanceId, "ShipOrder")
}
package examples.scala.application

import com.ing.baker.runtime.scaladsl.Baker

class ManualRetryInteraction {
  def retryExample(baker: Baker, recipeInstanceId: String): Unit = {
    baker.retryInteraction(recipeInstanceId, "ShipOrder")
  }
}

Resolve interaction

This method resolves a blocked interaction by firing an event.

package examples.java.application;

import com.ing.baker.runtime.javadsl.Baker;
import com.ing.baker.runtime.javadsl.EventInstance;

public class ManualResolveInteraction {

    public void resolveExample(Baker baker, String recipeInstanceId) {
        baker.resolveInteraction(
            recipeInstanceId,
            "ShipOrder",
            EventInstance.from("ShippingFailed")
        );
    }
}
package examples.kotlin.application

import com.ing.baker.runtime.javadsl.EventInstance
import com.ing.baker.runtime.kotlindsl.Baker

suspend fun resolveExample(baker: Baker, recipeInstanceId: String) {
    baker.resolveInteraction(
        recipeInstanceId,
        "ShipOrder",
        EventInstance.from("ShippingFailed")
    )
}
package examples.scala.application

import com.ing.baker.runtime.scaladsl.{Baker, EventInstance}

class ManualResolveInteraction {
  def resolveExample(baker: Baker, recipeInstanceId: String): Unit = {
    baker.resolveInteraction(
      recipeInstanceId,
      "ShipOrder",
      EventInstance("ShippingFailed")
    )
  }
}

Stop retrying

This method halts the retry process of a failing interaction by blocking the interaction.

package examples.java.application;

import com.ing.baker.runtime.javadsl.Baker;
import com.ing.baker.runtime.javadsl.EventInstance;

public class ManualStopInteraction {

    public void stopExample(Baker baker, String recipeInstanceId) {
        baker.stopRetryingInteraction(recipeInstanceId, "ShipOrder");
    }
}
package examples.kotlin.application

import com.ing.baker.runtime.kotlindsl.Baker

suspend fun stopExample(baker: Baker, recipeInstanceId: String) {
    baker.stopRetryingInteraction(recipeInstanceId, "ShipOrder")
}
package examples.scala.application

import com.ing.baker.runtime.scaladsl.Baker

class ManualStopInteraction {
  def retryExample(baker: Baker, recipeInstanceId: String): Unit = {
    baker.stopRetryingInteraction(recipeInstanceId, "ShipOrder")
  }
}