diff --git a/patches/api/0258-Add-StructuresLocateEvent.patch b/patches/api/0258-Add-StructuresLocateEvent.patch
index 9cc592eb3..a6f4e518b 100644
--- a/patches/api/0258-Add-StructuresLocateEvent.patch
+++ b/patches/api/0258-Add-StructuresLocateEvent.patch
@@ -340,7 +340,7 @@ index 0000000000000000000000000000000000000000..566f9df8f615142e14330965f3491f4e
 +}
 diff --git a/src/main/java/io/papermc/paper/world/structure/ConfiguredStructure.java b/src/main/java/io/papermc/paper/world/structure/ConfiguredStructure.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..110d24e988fcdaba0e4ad107d161b502f9f6572e
+index 0000000000000000000000000000000000000000..5a43e40b7311ed2acb51f6ba8b12d1f34569ff2e
 --- /dev/null
 +++ b/src/main/java/io/papermc/paper/world/structure/ConfiguredStructure.java
 @@ -0,0 +1,98 @@
@@ -393,7 +393,7 @@ index 0000000000000000000000000000000000000000..110d24e988fcdaba0e4ad107d161b502
 +    public static final Reference<ConfiguredStructure> RUINED_PORTAL_MOUNTAIN = create("ruined_portal_mountain");
 +    public static final Reference<ConfiguredStructure> RUINED_PORTAL_OCEAN = create("ruined_portal_ocean");
 +    public static final Reference<ConfiguredStructure> RUINED_PORTAL_NETHER = create("ruined_portal_nether");
-+    public static final Reference<ConfiguredStructure> ANCIENT_CITY = create("ancient_city");
++    // public static final Reference<ConfiguredStructure> ANCIENT_CITY = create("ancient_city"); // TODO remove when upstream adds "jigsaw" StructureType
 +
 +    private final NamespacedKey key;
 +    private final StructureType structureType;
diff --git a/patches/server/0568-Add-PaperRegistry.patch b/patches/server/0568-Add-PaperRegistry.patch
index 5ce8c0ebf..1a87131b1 100644
--- a/patches/server/0568-Add-PaperRegistry.patch
+++ b/patches/server/0568-Add-PaperRegistry.patch
@@ -7,10 +7,10 @@ PaperRegistry is a server-backed impl of bukkit's Registry interface
 
 diff --git a/src/main/java/io/papermc/paper/registry/PaperRegistry.java b/src/main/java/io/papermc/paper/registry/PaperRegistry.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..8d1f3c4891870b4239df678dd1e52e9f4ef74b2c
+index 0000000000000000000000000000000000000000..7c265d27da034986be73921d35bf08ae250b42f3
 --- /dev/null
 +++ b/src/main/java/io/papermc/paper/registry/PaperRegistry.java
-@@ -0,0 +1,147 @@
+@@ -0,0 +1,167 @@
 +package io.papermc.paper.registry;
 +
 +import com.google.common.base.Preconditions;
@@ -35,6 +35,7 @@ index 0000000000000000000000000000000000000000..8d1f3c4891870b4239df678dd1e52e9f
 +import java.util.Objects;
 +import java.util.Optional;
 +import java.util.concurrent.ConcurrentHashMap;
++import java.util.function.Consumer;
 +import java.util.function.Supplier;
 +
 +@DefaultQualifier(NonNull.class)
@@ -69,13 +70,21 @@ index 0000000000000000000000000000000000000000..8d1f3c4891870b4239df678dd1e52e9f
 +        });
 +    }
 +
-+    public abstract API convertToApi(NamespacedKey key, MINECRAFT nms);
++    public abstract @Nullable API convertToApi(NamespacedKey key, MINECRAFT nms);
 +
-+    public API convertToApi(ResourceLocation resourceLocation, MINECRAFT nms) {
++    public API convertToApiOrThrow(ResourceLocation resourceLocation, MINECRAFT nms) {
++        return Objects.requireNonNull(this.convertToApi(resourceLocation, nms), resourceLocation + " has a null api representation");
++    }
++
++    public @Nullable API convertToApi(ResourceLocation resourceLocation, MINECRAFT nms) {
 +        return this.convertToApi(CraftNamespacedKey.fromMinecraft(resourceLocation), nms);
 +    }
 +
-+    public API convertToApi(Holder<MINECRAFT> nmsHolder) {
++    public API convertToApiOrThrow(Holder<MINECRAFT> nmsHolder) {
++        return Objects.requireNonNull(this.convertToApi(nmsHolder), nmsHolder + " has a null api representation");
++    }
++
++    public @Nullable API convertToApi(Holder<MINECRAFT> nmsHolder) {
 +        final Optional<ResourceKey<MINECRAFT>> key = nmsHolder.unwrapKey();
 +        if (nmsHolder.isBound() && key.isPresent()) {
 +            return this.convertToApi(key.get().location(), nmsHolder.value());
@@ -90,6 +99,17 @@ index 0000000000000000000000000000000000000000..8d1f3c4891870b4239df678dd1e52e9f
 +        throw new IllegalStateException("Cannot convert " + nmsHolder + " to an API type in: " + this.registryKey);
 +    }
 +
++    public void convertToApi(Iterable<Holder<MINECRAFT>> holders, Consumer<API> apiConsumer, boolean throwOnNull) {
++        for (Holder<MINECRAFT> holder : holders) {
++            final @Nullable API api = this.convertToApi(holder);
++            if (api == null && throwOnNull) {
++                throw new NullPointerException(holder + " has a null api representation");
++            } else if (api != null) {
++                apiConsumer.accept(api);
++            }
++        }
++    }
++
 +    public MINECRAFT getMinecraftValue(API apiValue) {
 +        return this.registry.get().getOptional(CraftNamespacedKey.toMinecraft(apiValue.getKey())).orElseThrow();
 +    }
diff --git a/patches/server/0569-Add-StructuresLocateEvent.patch b/patches/server/0569-Add-StructuresLocateEvent.patch
index 43e605007..47c707787 100644
--- a/patches/server/0569-Add-StructuresLocateEvent.patch
+++ b/patches/server/0569-Add-StructuresLocateEvent.patch
@@ -25,10 +25,10 @@ index 6f39e343147803e15e7681c993b8797a629702e7..87154ae69788249960bca376aafd90bf
  }
 diff --git a/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..ec66a52c06aceb4e16b987e695e50dbe0f9e1c44
+index 0000000000000000000000000000000000000000..423bf87ebda7ea266dc7b48cbfadbc8551180721
 --- /dev/null
 +++ b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java
-@@ -0,0 +1,41 @@
+@@ -0,0 +1,42 @@
 +package io.papermc.paper.world.structure;
 +
 +import io.papermc.paper.registry.PaperRegistry;
@@ -39,6 +39,7 @@ index 0000000000000000000000000000000000000000..ec66a52c06aceb4e16b987e695e50dbe
 +import org.bukkit.NamespacedKey;
 +import org.bukkit.StructureType;
 +import org.checkerframework.checker.nullness.qual.NonNull;
++import org.checkerframework.checker.nullness.qual.Nullable;
 +import org.checkerframework.framework.qual.DefaultQualifier;
 +
 +import java.util.Objects;
@@ -63,18 +64,18 @@ index 0000000000000000000000000000000000000000..ec66a52c06aceb4e16b987e695e50dbe
 +        }
 +
 +        @Override
-+        public ConfiguredStructure convertToApi(NamespacedKey key, Structure nms) {
++        public @Nullable ConfiguredStructure convertToApi(NamespacedKey key, Structure nms) {
 +            final ResourceLocation structureTypeLoc = Objects.requireNonNull(Registry.STRUCTURE_TYPES.getKey(nms.type()), "unexpected structure type " + nms.type());
-+            final StructureType structureType = Objects.requireNonNull(StructureType.getStructureTypes().get(structureTypeLoc.getPath()), structureTypeLoc + " could not be converted to an API type"); // TODO this is just not gonna work until upstream fixes their StructureType pseudo-enum
-+            return new ConfiguredStructure(key, structureType);
++            final @Nullable StructureType structureType = StructureType.getStructureTypes().get(structureTypeLoc.getPath());
++            return structureType == null ? null : new ConfiguredStructure(key, structureType);
 +        }
 +    }
 +}
 diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
-index 0f92f2906195f5b2b70ca02a46fa111a46f8f18f..36940a873b2f891a50009fd44a2793c1940d2b05 100644
+index 0f92f2906195f5b2b70ca02a46fa111a46f8f18f..a0b21c6ffdc1a08472079db0cbfc36ec0155f2c4 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
-@@ -295,6 +295,26 @@ public abstract class ChunkGenerator {
+@@ -295,6 +295,24 @@ public abstract class ChunkGenerator {
  
      @Nullable
      public Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel world, HolderSet<Structure> structures, BlockPos center, int radius, boolean skipReferencedStructures) {
@@ -83,9 +84,7 @@ index 0f92f2906195f5b2b70ca02a46fa111a46f8f18f..36940a873b2f891a50009fd44a2793c1
 +        final org.bukkit.Location origin = net.minecraft.server.MCUtil.toLocation(world, center);
 +        final var paperRegistry = io.papermc.paper.registry.PaperRegistry.getRegistry(io.papermc.paper.registry.RegistryKey.CONFIGURED_STRUCTURE_REGISTRY);
 +        final List<io.papermc.paper.world.structure.ConfiguredStructure> configuredStructures = new ArrayList<>();
-+        for (Holder<Structure> holder : structures) {
-+            configuredStructures.add(paperRegistry.convertToApi(holder));
-+        }
++        paperRegistry.convertToApi(structures, configuredStructures::add, false); // gracefully handle missing api, use tests to check (or exclude)
 +        final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, configuredStructures, radius, skipReferencedStructures);
 +        if (!event.callEvent()) {
 +            return null;
@@ -115,10 +114,10 @@ index 737956b316c02e4ccdc6eef8de4a0a299d36b9ca..b8649eab719a1b71dc686386a8db756e
          return Structure.StructureSettings.CODEC.forGetter((feature) -> {
 diff --git a/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java
 new file mode 100644
-index 0000000000000000000000000000000000000000..9b07d3128cc788efd17ed6a6bdd3370a3d88b48b
+index 0000000000000000000000000000000000000000..61efebe1d363b34e2043ccc4c6e28bb714c2fa31
 --- /dev/null
 +++ b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java
-@@ -0,0 +1,89 @@
+@@ -0,0 +1,92 @@
 +package io.papermc.paper.world.structure;
 +
 +import io.papermc.paper.registry.Reference;
@@ -178,6 +177,9 @@ index 0000000000000000000000000000000000000000..9b07d3128cc788efd17ed6a6bdd3370a
 +        for (Structure feature : BuiltinRegistries.STRUCTURES) {
 +            final ResourceLocation key = BuiltinRegistries.STRUCTURES.getKey(feature);
 +            assertNotNull("Missing built-in registry key", key);
++            if (key.equals(BuiltinStructures.ANCIENT_CITY.location())) {
++                continue; // TODO remove when upstream adds "jigsaw" StructureType
++            }
 +            if (DEFAULT_CONFIGURED_STRUCTURES.get(CraftNamespacedKey.fromMinecraft(key)) == null) {
 +                missing.put(key, feature);
 +            }
diff --git a/patches/server/0783-Configurable-feature-seeds.patch b/patches/server/0783-Configurable-feature-seeds.patch
index e5a2c768c..a3ab38422 100644
--- a/patches/server/0783-Configurable-feature-seeds.patch
+++ b/patches/server/0783-Configurable-feature-seeds.patch
@@ -79,10 +79,10 @@ index 5c43fe70f8c87b0a83f10f9608ddca556e99e634..28b4188cd15d297e4b89ab98f78cecd7
          return getIntOrDefault(behaviorTickRates, typeName, entityType, def);
      }
 diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
-index 36940a873b2f891a50009fd44a2793c1940d2b05..05c8b3f54ecb4221dcbf37574240401d93e14e7a 100644
+index a0b21c6ffdc1a08472079db0cbfc36ec0155f2c4..974b948513c2b2c7f2503fbed044bcea01231938 100644
 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
 +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
-@@ -530,7 +530,7 @@ public abstract class ChunkGenerator {
+@@ -528,7 +528,7 @@ public abstract class ChunkGenerator {
              int j = list.size();
  
              try {
@@ -91,7 +91,7 @@ index 36940a873b2f891a50009fd44a2793c1940d2b05..05c8b3f54ecb4221dcbf37574240401d
                  int k = Math.max(GenerationStep.Decoration.values().length, j);
  
                  for (int l = 0; l < k; ++l) {
-@@ -603,7 +603,15 @@ public abstract class ChunkGenerator {
+@@ -601,7 +601,15 @@ public abstract class ChunkGenerator {
                                  return (String) optional.orElseGet(placedfeature::toString);
                              };