diff --git a/core/lib/Drupal/Core/Validation/ExecutionContext.php b/core/lib/Drupal/Core/Validation/ExecutionContext.php
index 5e67e0e786f03901ecdf9b5b852dd1e503cd226a..150c5a7b96548e58bd360f440a5328d4f97179bb 100644
--- a/core/lib/Drupal/Core/Validation/ExecutionContext.php
+++ b/core/lib/Drupal/Core/Validation/ExecutionContext.php
@@ -241,4 +241,11 @@ public function isObjectInitialized(string $cacheKey): bool {
     throw new \LogicException(ExecutionContextInterface::class . '::isObjectInitialized is unsupported.');
   }
 
+  /**
+   * Clone this context.
+   */
+  public function __clone(): void {
+    $this->violations = clone $this->violations;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AtLeastOneOfConstraint.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AtLeastOneOfConstraint.php
new file mode 100644
index 0000000000000000000000000000000000000000..de99cd9985aeb76d0fd26c748a5e0c779b6336b7
--- /dev/null
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AtLeastOneOfConstraint.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Validation\Attribute\Constraint;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\Constraint as SymfonyConstraint;
+use Symfony\Component\Validator\Constraints\AtLeastOneOf;
+
+/**
+ * Checks that at least one of the given constraint is satisfied.
+ *
+ * Overrides the symfony constraint to convert the array of constraints to array
+ * of constraint objects and use them.
+ */
+#[Constraint(
+  id: 'AtLeastOneOf',
+  label: new TranslatableMarkup('At least one of', [], ['context' => 'Validation'])
+)]
+class AtLeastOneOfConstraint extends AtLeastOneOf implements ContainerFactoryPluginInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    $constraint_manager = $container->get('validation.constraint');
+    $constraints = $configuration['constraints'];
+    $constraint_instances = [];
+    foreach ($constraints as $constraint_id => $constraint) {
+      foreach ($constraint as $constraint_name => $constraint_options) {
+        $constraint_instances[$constraint_id] = $constraint_manager->create($constraint_name, $constraint_options);
+      }
+    }
+
+    return new static($constraint_instances, [SymfonyConstraint::DEFAULT_GROUP]);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AtLeastOneOfConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AtLeastOneOfConstraintValidator.php
new file mode 100644
index 0000000000000000000000000000000000000000..15a5cbb5d79338c675f24398185b94dee1ddbe10
--- /dev/null
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/AtLeastOneOfConstraintValidator.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Drupal\Core\Validation\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\Constraints\All;
+use Symfony\Component\Validator\Constraints\Collection;
+use Symfony\Component\Validator\ConstraintValidator;
+use Symfony\Component\Validator\Exception\UnexpectedTypeException;
+
+/**
+ * Validates the AtLeastOneOf constraint.
+ */
+class AtLeastOneOfConstraintValidator extends ConstraintValidator {
+
+  /**
+   * Validate a set of constraints against a value.
+   *
+   * This validator method is a copy of Symfony's AtLeastOneOf constraint. This
+   * is necessary because Drupal does not support validation groups.
+   *
+   * @param mixed $value
+   *   The value to validate.
+   * @param \Symfony\Component\Validator\Constraint $constraint
+   *   The constraint to validate against.
+   */
+  public function validate(mixed $value, Constraint $constraint): void {
+    if (!$constraint instanceof AtLeastOneOfConstraint) {
+      throw new UnexpectedTypeException($constraint, AtLeastOneOfConstraint::class);
+    }
+
+    $validator = $this->context->getValidator();
+
+    // Build a first violation to have the base message of the constraint.
+    $baseMessageContext = clone $this->context;
+    $baseMessageContext->buildViolation($constraint->message)->addViolation();
+    $baseViolations = $baseMessageContext->getViolations();
+    $messages = [(string) $baseViolations->get(\count($baseViolations) - 1)->getMessage()];
+
+    foreach ($constraint->constraints as $key => $item) {
+      $context_group = $this->context->getGroup();
+      if (!\in_array($context_group, $item->groups, TRUE)) {
+        continue;
+      }
+
+      $context = $this->context;
+      $executionContext = clone $this->context;
+      $executionContext->setNode($value, $this->context->getObject(), $this->context->getMetadata(), $this->context->getPropertyPath());
+      $violations = $validator->inContext($executionContext)->validate($context->getObject(), $item/*, $context_group*/)->getViolations();
+      $this->context = $context;
+
+      if (\count($this->context->getViolations()) === \count($violations)) {
+        return;
+      }
+
+      if ($constraint->includeInternalMessages) {
+        $message = ' [' . ($key + 1) . '] ';
+
+        if ($item instanceof All || $item instanceof Collection) {
+          $message .= $constraint->messageCollection;
+        }
+        else {
+          $message .= $violations->get(\count($violations) - 1)->getMessage();
+        }
+
+        $messages[] = $message;
+      }
+    }
+
+    $this->context
+      ->buildViolation(implode('', $messages))
+      ->addViolation();
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Validation/AtLeastOneOfConstraintValidatorTest.php b/core/tests/Drupal/KernelTests/Core/Validation/AtLeastOneOfConstraintValidatorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1afd6b2124ef2786d5a505a1d99fb8303ea575ee
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Validation/AtLeastOneOfConstraintValidatorTest.php
@@ -0,0 +1,115 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\KernelTests\Core\Validation;
+
+use Drupal\Core\TypedData\DataDefinition;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Tests AtLeastOneOf validation constraint with both valid and invalid values.
+ *
+ * @covers \Drupal\Core\Validation\Plugin\Validation\Constraint\AtLeastOneOfConstraint
+ * @covers \Drupal\Core\Validation\Plugin\Validation\Constraint\AtLeastOneOfConstraintValidator
+ *
+ * @group Validation
+ */
+class AtLeastOneOfConstraintValidatorTest extends KernelTestBase {
+
+  /**
+   * The typed data manager to use.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataManager
+   */
+  protected $typedData;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    $this->typedData = $this->container->get('typed_data_manager');
+  }
+
+  /**
+   * Tests the AllowedValues validation constraint validator.
+   *
+   * For testing we define an integer with a set of allowed values.
+   *
+   * @dataProvider dataProvider
+   */
+  public function testValidation($type, $value, $at_least_one_of_constraints, $expectedViolations, $extra_constraints = []): void {
+    // Create a definition that specifies some AllowedValues.
+    $definition = DataDefinition::create($type);
+
+    if (count($extra_constraints) > 0) {
+      foreach ($extra_constraints as $name => $settings) {
+        $definition->addConstraint($name, $settings);
+      }
+    }
+
+    $definition->addConstraint('AtLeastOneOf', [
+      'constraints' => $at_least_one_of_constraints,
+    ]);
+
+    // Test the validation.
+    $typed_data = $this->typedData->create($definition, $value);
+    $violations = $typed_data->validate();
+
+    $violationMessages = [];
+    foreach ($violations as $violation) {
+      $violationMessages[] = (string) $violation->getMessage();
+    }
+
+    $this->assertEquals($expectedViolations, $violationMessages, 'Validation passed for correct value.');
+  }
+
+  /**
+   * Data provider for testValidation().
+   */
+  public static function dataProvider(): array {
+    return [
+      'It should fail on a failing sibling validator' => [
+        'integer',
+        1,
+        [
+          ['Range' => ['min' => 100]],
+          ['NotNull' => []],
+        ],
+        ['This value should be blank.'],
+        ['Blank' => []],
+      ],
+      'it should not fail if first validator fails' => [
+        'integer',
+        250,
+        [
+          ['AllowedValues' => [500]],
+          ['Range' => ['min' => 100]],
+        ],
+        [],
+      ],
+      'it should not fail if second validator fails' => [
+        'integer',
+        250,
+        [
+          ['Range' => ['min' => 100]],
+          ['AllowedValues' => [500]],
+        ],
+        [],
+      ],
+      'it should show multiple validation errors if none validate' => [
+        'string',
+        'Green',
+        [
+          ['AllowedValues' => ['test']],
+          ['Blank' => []],
+        ],
+        [
+          'This value should satisfy at least one of the following constraints: [1] The value you selected is not a valid choice. [2] This value should be blank.',
+        ],
+      ],
+    ];
+  }
+
+}