This is an automated email from the ASF dual-hosted git repository.

scwhittle pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new 66ecc31a22c Adds a new CoderTranslator for Java SchemaCoders. (#39594)
66ecc31a22c is described below

commit 66ecc31a22c9fa39dbf70965f17d10b114022185
Author: Andrew Crites <[email protected]>
AuthorDate: Tue Aug 25 00:42:07 2026 -0700

    Adds a new CoderTranslator for Java SchemaCoders. (#39594)
    
    The updateCompatabilityVersion option can be used to select the previous 
encoding.
---
 CHANGES.md                                         |  4 +-
 .../dataflow/DataflowPipelineTranslator.java       |  2 +-
 .../beam/runners/dataflow/DataflowRunner.java      | 54 ++++++++-------
 .../dataflow/DataflowPipelineTranslatorTest.java   | 78 +++++++++++++++++----
 .../control/ProcessBundleDescriptorsTest.java      |  6 +-
 .../sdk/util/construction/CoderTranslation.java    | 62 +++++++++++++++--
 .../construction/CoderTranslatorRegistrar.java     | 16 +++++
 .../sdk/util/construction/CoderTranslators.java    | 79 ++++++++++++++++++++++
 .../sdk/util/construction/ModelCoderRegistrar.java | 28 ++++++--
 .../beam/sdk/util/construction/ModelCoders.java    |  2 +
 .../util/construction/RehydratedComponents.java    |  3 +-
 .../beam/sdk/util/construction/SdkComponents.java  | 39 ++++++-----
 .../util/construction/CoderTranslationTest.java    | 38 ++++++++++-
 .../extensions/avro/AvroGenericCoderRegistrar.java | 18 +++++
 .../beam/fn/harness/state/StateBackedIterable.java | 22 ++++++
 .../runners/portability/fn_api_runner/execution.py |  4 +-
 .../portability/fn_api_runner/translations.py      |  4 +-
 17 files changed, 386 insertions(+), 73 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 3994aea4d93..3f097d96dab 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -74,7 +74,9 @@
 
 ## Breaking Changes
 
-* X behavior was changed ([#X](https://github.com/apache/beam/issues/X)).
+* Portable Java SDK now encodes SchemaCoders in a portable way 
([#34672](https://github.com/apache/beam/issues/34672)).
+  - Original custom Java coder encoding can still be obtained using 
[StreamingOptions.setUpdateCompatibilityVersion("2.76")](https://github.com/apache/beam/blob/2cf0930e7ae1aa389c26ce6639b584877a3e31d9/sdks/java/core/src/main/java/org/apache/beam/sdk/options/StreamingOptions.java#L47)
 ([#34672](https://github.com/apache/beam/issues/34672)).
+  - Fixes ([#36496](https://github.com/apache/beam/issues/36496)), 
([#30276](https://github.com/apache/beam/issues/30276)), 
([#29245](https://github.com/apache/beam/issues/29245)).
 
 ## Deprecations
 
diff --git 
a/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/DataflowPipelineTranslator.java
 
b/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/DataflowPipelineTranslator.java
index 4016f31a547..1609cf6ea23 100644
--- 
a/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/DataflowPipelineTranslator.java
+++ 
b/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/DataflowPipelineTranslator.java
@@ -221,7 +221,7 @@ public class DataflowPipelineTranslator {
   private static byte[] serializeWindowingStrategy(
       WindowingStrategy<?, ?> windowingStrategy, PipelineOptions options) {
     try {
-      SdkComponents sdkComponents = SdkComponents.create();
+      SdkComponents sdkComponents = SdkComponents.create(options);
 
       String workerHarnessContainerImageURL =
           
DataflowRunner.getContainerImageForJob(options.as(DataflowPipelineOptions.class));
diff --git 
a/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/DataflowRunner.java
 
b/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/DataflowRunner.java
index 1b03cc10351..ee749901ec6 100644
--- 
a/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/DataflowRunner.java
+++ 
b/runners/google-cloud-dataflow-java/src/main/java/org/apache/beam/runners/dataflow/DataflowRunner.java
@@ -1355,19 +1355,20 @@ public class DataflowRunner extends 
PipelineRunner<DataflowPipelineJob> {
     // with the SDK harness image (which implements Fn API).
     //
     // The same Environment is used in different and contradictory ways, 
depending on whether
-    // it is a v1 or v2 job submission.
+    // it is a portable or non-portable job submission.
     RunnerApi.Environment defaultEnvironmentForDataflow =
         Environments.createDockerEnvironment(workerHarnessContainerImageURL);
 
-    // The SdkComponents for portable an non-portable job submission must be 
kept distinct. Both
+    // The SdkComponents for portable and non-portable job submission must be 
kept distinct. Both
     // need the default environment.
-    SdkComponents portableComponents = SdkComponents.create();
-    portableComponents.registerEnvironment(
-        defaultEnvironmentForDataflow
-            .toBuilder()
-            .addAllDependencies(getDefaultArtifacts())
-            .addAllCapabilities(Environments.getJavaCapabilities())
-            .build());
+    SdkComponents portableComponents =
+        SdkComponents.create(
+            options,
+            defaultEnvironmentForDataflow
+                .toBuilder()
+                .addAllDependencies(getDefaultArtifacts())
+                .addAllCapabilities(Environments.getJavaCapabilities())
+                .build());
 
     RunnerApi.Pipeline portablePipelineProto =
         PipelineTranslation.toProto(pipeline, portableComponents, false);
@@ -1400,27 +1401,28 @@ public class DataflowRunner extends 
PipelineRunner<DataflowPipelineJob> {
           "Skipping Dataflow Streaming Java Runner transform replacements 
since job will run on Dataflow Portable Runner.");
     } else {
       // Now rewrite things to be as needed for Dataflow Streaming Java Runner 
(mutates the
-      // pipeline)
+      // pipeline).
       // This way the job submitted is valid for Dataflow Streaming Java 
Runner and Dataflow
-      // Portable Runner, simultaneously
+      // Portable Runner, simultaneously.
       replaceV1Transforms(pipeline);
     }
-    // Capture the SdkComponents for look up during step translations
-    SdkComponents dataflowV1Components = SdkComponents.create();
-    dataflowV1Components.registerEnvironment(
-        defaultEnvironmentForDataflow
-            .toBuilder()
-            .addAllDependencies(getDefaultArtifacts())
-            .addAllCapabilities(Environments.getJavaCapabilities())
-            .build());
+    // Capture the SdkComponents for look up during step translations.
+    SdkComponents dataflowNonPortableComponents =
+        SdkComponents.create(
+            options,
+            defaultEnvironmentForDataflow
+                .toBuilder()
+                .addAllDependencies(getDefaultArtifacts())
+                .addAllCapabilities(Environments.getJavaCapabilities())
+                .build());
     // No need to perform transform upgrading for the Dataflow Streaming Java 
Runner proto.
-    RunnerApi.Pipeline dataflowV1PipelineProto =
-        PipelineTranslation.toProto(pipeline, dataflowV1Components, true, 
false);
+    RunnerApi.Pipeline dataflowNonPortablePipelineProto =
+        PipelineTranslation.toProto(pipeline, dataflowNonPortableComponents, 
true, false);
 
     if (LOG.isDebugEnabled()) {
       LOG.debug(
-          "Dataflow v1 pipeline proto:\n{}",
-          TextFormat.printer().printToString(dataflowV1PipelineProto));
+          "Dataflow non-portable worker pipeline proto:\n{}",
+          
TextFormat.printer().printToString(dataflowNonPortablePipelineProto));
     }
 
     // Set a unique client_request_id in the CreateJob request.
@@ -1440,7 +1442,11 @@ public class DataflowRunner extends 
PipelineRunner<DataflowPipelineJob> {
 
     JobSpecification jobSpecification =
         translator.translate(
-            pipeline, dataflowV1PipelineProto, dataflowV1Components, this, 
packages);
+            pipeline,
+            dataflowNonPortablePipelineProto,
+            dataflowNonPortableComponents,
+            this,
+            packages);
 
     if (!isNullOrEmpty(dataflowOptions.getDataflowWorkerJar()) && 
!useUnifiedWorker(options)) {
       List<String> experiments =
diff --git 
a/runners/google-cloud-dataflow-java/src/test/java/org/apache/beam/runners/dataflow/DataflowPipelineTranslatorTest.java
 
b/runners/google-cloud-dataflow-java/src/test/java/org/apache/beam/runners/dataflow/DataflowPipelineTranslatorTest.java
index f8818931a68..127402f6f54 100644
--- 
a/runners/google-cloud-dataflow-java/src/test/java/org/apache/beam/runners/dataflow/DataflowPipelineTranslatorTest.java
+++ 
b/runners/google-cloud-dataflow-java/src/test/java/org/apache/beam/runners/dataflow/DataflowPipelineTranslatorTest.java
@@ -47,6 +47,7 @@ import com.google.api.services.dataflow.Dataflow;
 import com.google.api.services.dataflow.model.Job;
 import com.google.api.services.dataflow.model.Step;
 import com.google.api.services.dataflow.model.WorkerPool;
+import com.google.auto.value.AutoValue;
 import java.io.File;
 import java.io.IOException;
 import java.io.Serializable;
@@ -92,6 +93,8 @@ import org.apache.beam.sdk.options.PipelineOptions;
 import org.apache.beam.sdk.options.PipelineOptionsFactory;
 import org.apache.beam.sdk.options.StreamingOptions;
 import org.apache.beam.sdk.options.ValueProvider;
+import org.apache.beam.sdk.schemas.AutoValueSchema;
+import org.apache.beam.sdk.schemas.annotations.DefaultSchema;
 import org.apache.beam.sdk.state.StateSpec;
 import org.apache.beam.sdk.state.StateSpecs;
 import org.apache.beam.sdk.state.ValueState;
@@ -166,15 +169,11 @@ public class DataflowPipelineTranslatorTest implements 
Serializable {
   @Rule public transient ExpectedException thrown = ExpectedException.none();
 
   private SdkComponents createSdkComponents(PipelineOptions options) {
-    SdkComponents sdkComponents = SdkComponents.create();
-
     String containerImageURL =
         
DataflowRunner.getContainerImageForJob(options.as(DataflowPipelineOptions.class));
     RunnerApi.Environment defaultEnvironmentForDataflow =
         Environments.createDockerEnvironment(containerImageURL);
-
-    sdkComponents.registerEnvironment(defaultEnvironmentForDataflow);
-    return sdkComponents;
+    return SdkComponents.create(options, defaultEnvironmentForDataflow);
   }
 
   // A Custom Mockito matcher for an initial Job that checks that all
@@ -1294,15 +1293,16 @@ public class DataflowPipelineTranslatorTest implements 
Serializable {
     file1.deleteOnExit();
     File file2 = File.createTempFile("file2-", ".txt");
     file2.deleteOnExit();
-    SdkComponents sdkComponents = SdkComponents.create();
-    sdkComponents.registerEnvironment(
-        
Environments.createDockerEnvironment(DataflowRunner.getContainerImageForJob(options))
-            .toBuilder()
-            .addAllDependencies(
-                Environments.getArtifacts(
-                    ImmutableList.of("file1.txt=" + file1, "file2.txt=" + 
file2)))
-            .addAllCapabilities(Environments.getJavaCapabilities())
-            .build());
+    SdkComponents sdkComponents =
+        SdkComponents.create(
+            options,
+            
Environments.createDockerEnvironment(DataflowRunner.getContainerImageForJob(options))
+                .toBuilder()
+                .addAllDependencies(
+                    Environments.getArtifacts(
+                        ImmutableList.of("file1.txt=" + file1, "file2.txt=" + 
file2)))
+                .addAllCapabilities(Environments.getJavaCapabilities())
+                .build());
 
     RunnerApi.Pipeline pipelineProto = PipelineTranslation.toProto(pipeline, 
sdkComponents, true);
 
@@ -1870,4 +1870,54 @@ public class DataflowPipelineTranslatorTest implements 
Serializable {
       return null;
     }
   }
+
+  @AutoValue
+  @DefaultSchema(AutoValueSchema.class)
+  public abstract static class SimpleAutoValue {
+    public abstract String getString();
+
+    public abstract int getInt32();
+
+    public abstract long getInt64();
+
+    public static DataflowPipelineTranslatorTest.SimpleAutoValue of(
+        String string, int int32, long int64) {
+      return new 
AutoValue_DataflowPipelineTranslatorTest_SimpleAutoValue(string, int32, int64);
+    }
+  }
+
+  @Test
+  public void testSchemaCoderTranslation() throws Exception {
+    DataflowPipelineOptions options = buildPipelineOptions();
+    Pipeline pipeline = Pipeline.create(options);
+    pipeline
+        .apply(Impulse.create())
+        .apply(
+            MapElements.via(
+                new SimpleFunction<byte[], SimpleAutoValue>() {
+                  @Override
+                  public SimpleAutoValue apply(byte[] input) {
+                    return SimpleAutoValue.of("foo", 5, 10L);
+                  }
+                }))
+        .apply(Window.into(FixedWindows.of(Duration.standardMinutes(1))));
+    {
+      // By default, new pipelines get the new schema coder encoding.
+      SdkComponents sdkComponents = createSdkComponents(options);
+      RunnerApi.Pipeline pipelineProto = PipelineTranslation.toProto(pipeline, 
sdkComponents, true);
+      Map<String, RunnerApi.Coder> coders = 
pipelineProto.getComponents().getCodersMap();
+      assertTrue(coders.containsKey("SchemaCoder"));
+      assertEquals("beam:coder:schema:v1", 
coders.get("SchemaCoder").getSpec().getUrn());
+    }
+
+    {
+      // Check that we get the java coder encoding at version 2.76 (and 
earlier).
+      options.as(StreamingOptions.class).setUpdateCompatibilityVersion("2.76");
+      SdkComponents sdkComponents = createSdkComponents(options);
+      RunnerApi.Pipeline pipelineProto = PipelineTranslation.toProto(pipeline, 
sdkComponents, true);
+      Map<String, RunnerApi.Coder> coders = 
pipelineProto.getComponents().getCodersMap();
+      assertTrue(coders.containsKey("SchemaCoder"));
+      assertEquals("beam:coders:javasdk:0.1", 
coders.get("SchemaCoder").getSpec().getUrn());
+    }
+  }
 }
diff --git 
a/runners/java-fn-execution/src/test/java/org/apache/beam/runners/fnexecution/control/ProcessBundleDescriptorsTest.java
 
b/runners/java-fn-execution/src/test/java/org/apache/beam/runners/fnexecution/control/ProcessBundleDescriptorsTest.java
index 21d7550c38b..9ea7404053d 100644
--- 
a/runners/java-fn-execution/src/test/java/org/apache/beam/runners/fnexecution/control/ProcessBundleDescriptorsTest.java
+++ 
b/runners/java-fn-execution/src/test/java/org/apache/beam/runners/fnexecution/control/ProcessBundleDescriptorsTest.java
@@ -78,7 +78,8 @@ public class ProcessBundleDescriptorsTest implements 
Serializable {
     // Add another stateful stage with a non-standard key coder
     Pipeline p = Pipeline.create();
     Coder<Void> keycoder = VoidCoder.of();
-    assertThat(ModelCoderRegistrar.isKnownCoder(keycoder), is(false));
+    ModelCoderRegistrar coderRegistrar = new ModelCoderRegistrar();
+    assertThat(coderRegistrar.isKnownCoder(keycoder, p.getOptions()), 
is(false));
     p.apply("impulse", Impulse.create())
         .apply(
             "create",
@@ -165,7 +166,8 @@ public class ProcessBundleDescriptorsTest implements 
Serializable {
   public void testLengthPrefixingOfInputCoderExecutableStage() throws 
Exception {
     Pipeline p = Pipeline.create();
     Coder<Void> voidCoder = VoidCoder.of();
-    assertThat(ModelCoderRegistrar.isKnownCoder(voidCoder), is(false));
+    ModelCoderRegistrar coderRegistrar = new ModelCoderRegistrar();
+    assertThat(coderRegistrar.isKnownCoder(voidCoder, p.getOptions()), 
is(false));
     p.apply("impulse", Impulse.create())
         .apply(
             ParDo.of(
diff --git 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/CoderTranslation.java
 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/CoderTranslation.java
index 22859dc68b9..2cc4bf0c6a0 100644
--- 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/CoderTranslation.java
+++ 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/CoderTranslation.java
@@ -25,11 +25,13 @@ import java.util.ServiceLoader;
 import org.apache.beam.model.pipeline.v1.RunnerApi;
 import org.apache.beam.model.pipeline.v1.RunnerApi.FunctionSpec;
 import org.apache.beam.sdk.coders.Coder;
+import org.apache.beam.sdk.options.PipelineOptions;
 import org.apache.beam.sdk.util.SerializableUtils;
 import org.apache.beam.vendor.grpc.v1p69p0.com.google.protobuf.ByteString;
 import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.annotations.VisibleForTesting;
 import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.BiMap;
 import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableBiMap;
+import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableList;
 import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap;
 import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
 import org.checkerframework.dataflow.qual.Deterministic;
@@ -62,6 +64,8 @@ public class CoderTranslation {
 
   private static @MonotonicNonNull BiMap<Class<? extends Coder>, String> 
knownCoderUrns;
 
+  private static @MonotonicNonNull List<CoderTranslatorRegistrar> 
coderTranslatorRegistrars;
+
   private static @MonotonicNonNull Map<Class<? extends Coder>, 
CoderTranslator<? extends Coder>>
       knownTranslators;
 
@@ -80,6 +84,53 @@ public class CoderTranslation {
     return knownCoderUrns;
   }
 
+  private static void initializeCoderTranslatorRegistrars() {
+    ImmutableList.Builder<CoderTranslatorRegistrar> registrars = 
ImmutableList.builder();
+    for (CoderTranslatorRegistrar coderTranslatorRegistrar :
+        ServiceLoader.load(CoderTranslatorRegistrar.class)) {
+      registrars.add(coderTranslatorRegistrar);
+    }
+    coderTranslatorRegistrars = registrars.build();
+  }
+
+  static boolean isKnownCoder(Coder<?> coder, PipelineOptions options) {
+    if (coderTranslatorRegistrars == null) {
+      initializeCoderTranslatorRegistrars();
+    }
+    for (CoderTranslatorRegistrar registrar : coderTranslatorRegistrars) {
+      if (registrar.isKnownCoder(coder, options)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  static CoderTranslator<? extends Coder> getCoderTranslator(Class<? extends 
Coder> coderClass) {
+    if (coderTranslatorRegistrars == null) {
+      initializeCoderTranslatorRegistrars();
+    }
+    for (CoderTranslatorRegistrar registrar : coderTranslatorRegistrars) {
+      CoderTranslator translator = registrar.getCoderTranslator(coderClass);
+      if (translator != null) {
+        return translator;
+      }
+    }
+    return null;
+  }
+
+  static Class<? extends Coder> getCoderForUrn(String coderUrn) {
+    if (coderTranslatorRegistrars == null) {
+      initializeCoderTranslatorRegistrars();
+    }
+    for (CoderTranslatorRegistrar registrar : coderTranslatorRegistrars) {
+      Class<? extends Coder> coder = registrar.getCoderForUrn(coderUrn);
+      if (coder != null) {
+        return coder;
+      }
+    }
+    return null;
+  }
+
   @VisibleForTesting
   @Deterministic
   static Map<Class<? extends Coder>, CoderTranslator<? extends Coder>> 
getKnownTranslators() {
@@ -107,7 +158,7 @@ public class CoderTranslation {
 
   public static RunnerApi.Coder toProto(Coder<?> coder, SdkComponents 
components)
       throws IOException {
-    if (getKnownCoderUrns().containsKey(coder.getClass())) {
+    if (isKnownCoder(coder, components.getPipelineOptions())) {
       return toKnownCoder(coder, components);
     }
 
@@ -129,7 +180,10 @@ public class CoderTranslation {
 
   private static RunnerApi.Coder toKnownCoder(Coder<?> coder, SdkComponents 
components)
       throws IOException {
-    CoderTranslator translator = getKnownTranslators().get(coder.getClass());
+    CoderTranslator translator = getCoderTranslator(coder.getClass());
+    if (translator == null) {
+      throw new IOException("Unable to find CoderTranslator for known Coder");
+    }
     List<String> componentIds = registerComponents(coder, translator, 
components);
     return RunnerApi.Coder.newBuilder()
         .addAllComponentCoderIds(componentIds)
@@ -186,8 +240,8 @@ public class CoderTranslation {
                   components.getComponents().getCodersOrThrow(componentId), 
components, context);
       coderComponents.add(innerCoder);
     }
-    Class<? extends Coder> coderType = 
getKnownCoderUrns().inverse().get(coderUrn);
-    CoderTranslator<?> translator = getKnownTranslators().get(coderType);
+    Class<? extends Coder> coderType = getCoderForUrn(coderUrn);
+    CoderTranslator<?> translator = getCoderTranslator(coderType);
     if (translator != null) {
       return translator.fromComponents(
           coderComponents, coder.getSpec().getPayload().toByteArray(), 
context);
diff --git 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/CoderTranslatorRegistrar.java
 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/CoderTranslatorRegistrar.java
index b69d0290de5..44e8c2956ae 100644
--- 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/CoderTranslatorRegistrar.java
+++ 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/CoderTranslatorRegistrar.java
@@ -19,6 +19,8 @@ package org.apache.beam.sdk.util.construction;
 
 import java.util.Map;
 import org.apache.beam.sdk.coders.Coder;
+import org.apache.beam.sdk.options.PipelineOptions;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /** A registrar of {@link Coder} URNs to the associated {@link 
CoderTranslator}. */
 @SuppressWarnings({
@@ -34,4 +36,18 @@ public interface CoderTranslatorRegistrar {
 
   /** Returns a mapping of URN to {@link CoderTranslator}. */
   Map<Class<? extends Coder>, CoderTranslator<? extends Coder>> 
getCoderTranslators();
+
+  /**
+   * Returns whether the given Coder is known to this 
CoderTranslatorRegistrar. If the Coder is
+   * known, then getCoderTranslator() will return a non-null CoderTranslator.
+   */
+  boolean isKnownCoder(Coder<?> coder, PipelineOptions options);
+
+  /** Returns the CoderTranslator to use for this Coder, or null if the Coder 
is not known. */
+  @Nullable
+  CoderTranslator<? extends Coder> getCoderTranslator(Class<? extends Coder> 
coderClass);
+
+  /** Returns the Coder to use for the given Urn, or null if the Urn is for an 
unknown Coder. */
+  @Nullable
+  Class<? extends Coder> getCoderForUrn(String coderUrn);
 }
diff --git 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/CoderTranslators.java
 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/CoderTranslators.java
index 84a90721a98..a847bf780df 100644
--- 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/CoderTranslators.java
+++ 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/CoderTranslators.java
@@ -19,6 +19,7 @@ package org.apache.beam.sdk.util.construction;
 
 import static 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.base.Preconditions.checkArgument;
 
+import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 import org.apache.beam.model.pipeline.v1.SchemaApi;
@@ -30,12 +31,19 @@ import org.apache.beam.sdk.coders.NullableCoder;
 import org.apache.beam.sdk.coders.RowCoder;
 import org.apache.beam.sdk.coders.TimestampPrefixingWindowCoder;
 import org.apache.beam.sdk.schemas.Schema;
+import org.apache.beam.sdk.schemas.SchemaCoder;
 import org.apache.beam.sdk.schemas.SchemaTranslation;
+import org.apache.beam.sdk.transforms.SerializableFunction;
 import org.apache.beam.sdk.transforms.windowing.BoundedWindow;
 import org.apache.beam.sdk.util.InstanceBuilder;
+import org.apache.beam.sdk.util.SerializableUtils;
 import org.apache.beam.sdk.util.ShardedKey;
+import 
org.apache.beam.sdk.util.construction.CoderTranslation.TranslationContext;
+import org.apache.beam.sdk.values.Row;
+import org.apache.beam.sdk.values.TypeDescriptor;
 import org.apache.beam.sdk.values.WindowedValues;
 import org.apache.beam.sdk.values.WindowedValues.FullWindowedValueCoder;
+import org.apache.beam.vendor.grpc.v1p69p0.com.google.protobuf.ByteString;
 import 
org.apache.beam.vendor.grpc.v1p69p0.com.google.protobuf.InvalidProtocolBufferException;
 import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableList;
 
@@ -177,6 +185,77 @@ class CoderTranslators {
     };
   }
 
+  static <T> CoderTranslator<SchemaCoder<T>> schema() {
+    return new CoderTranslator<SchemaCoder<T>>() {
+      private static final String TO_ROW_FUNCTION_URN = 
"beam:torowfn:javasdk:v1";
+      private static final String FROM_ROW_FUNCTION_URN = 
"beam:fromrowfn:javasdk:v1";
+      private static final String TYPE_DESCRIPTOR_URN = 
"beam:typedescriptor:javasdk:v1";
+
+      @Override
+      public ImmutableList<? extends Coder<?>> getComponents(SchemaCoder<T> 
from) {
+        return ImmutableList.of();
+      }
+
+      @Override
+      public byte[] getPayload(SchemaCoder<T> from) {
+        SchemaApi.SchemaCoderPayload.Builder payload = 
SchemaApi.SchemaCoderPayload.newBuilder();
+        payload.setSchema(SchemaTranslation.schemaToProto(from.getSchema(), 
true));
+        payload
+            .getToRowFnBuilder()
+            .setUrn(TO_ROW_FUNCTION_URN)
+            .setPayload(
+                ByteString.copyFrom(
+                    
SerializableUtils.serializeToByteArray(from.getToRowFunction())));
+        payload
+            .getFromRowFnBuilder()
+            .setUrn(FROM_ROW_FUNCTION_URN)
+            .setPayload(
+                ByteString.copyFrom(
+                    
SerializableUtils.serializeToByteArray(from.getFromRowFunction())));
+        payload
+            .addAdditionalCoderInfosBuilder()
+            .setUrn(TYPE_DESCRIPTOR_URN)
+            .setPayload(
+                ByteString.copyFrom(
+                    
SerializableUtils.serializeToByteArray(from.getEncodedTypeDescriptor())));
+        return payload.build().toByteArray();
+      }
+
+      @Override
+      public SchemaCoder<T> fromComponents(
+          List<Coder<?>> components, byte[] payload, TranslationContext 
context) {
+        checkArgument(
+            components.isEmpty(), "Expected empty component list, but 
received: %s", components);
+        try {
+          SchemaApi.SchemaCoderPayload schemaCoderPayload =
+              SchemaApi.SchemaCoderPayload.parseFrom(payload);
+          if (schemaCoderPayload.getAdditionalCoderInfosCount() == 0) {
+            throw new IllegalArgumentException("Missing serialized 
typeDescriptor");
+          }
+          TypeDescriptor<T> typeDescriptor =
+              (TypeDescriptor<T>)
+                  SerializableUtils.deserializeFromByteArray(
+                      
schemaCoderPayload.getAdditionalCoderInfos(0).getPayload().toByteArray(),
+                      "typeDescriptor");
+          SerializableFunction<T, Row> toRowFunction =
+              (SerializableFunction<T, Row>)
+                  SerializableUtils.deserializeFromByteArray(
+                      
schemaCoderPayload.getToRowFn().getPayload().toByteArray(), "toRowFunction");
+          SerializableFunction<Row, T> fromRowFunction =
+              (SerializableFunction<Row, T>)
+                  SerializableUtils.deserializeFromByteArray(
+                      
schemaCoderPayload.getFromRowFn().getPayload().toByteArray(),
+                      "fromRowFunction");
+
+          Schema schema = 
SchemaTranslation.schemaFromProto(schemaCoderPayload.getSchema());
+          return SchemaCoder.of(schema, typeDescriptor, toRowFunction, 
fromRowFunction);
+        } catch (IOException | IllegalArgumentException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    };
+  }
+
   static CoderTranslator<ShardedKey.Coder<?>> shardedKey() {
     return new SimpleStructuredCoderTranslator<ShardedKey.Coder<?>>() {
       @Override
diff --git 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/ModelCoderRegistrar.java
 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/ModelCoderRegistrar.java
index 5b0d5aedd61..5728aacd805 100644
--- 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/ModelCoderRegistrar.java
+++ 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/ModelCoderRegistrar.java
@@ -34,6 +34,9 @@ import org.apache.beam.sdk.coders.RowCoder;
 import org.apache.beam.sdk.coders.StringUtf8Coder;
 import org.apache.beam.sdk.coders.TimestampPrefixingWindowCoder;
 import org.apache.beam.sdk.coders.VarLongCoder;
+import org.apache.beam.sdk.options.PipelineOptions;
+import org.apache.beam.sdk.options.StreamingOptions;
+import org.apache.beam.sdk.schemas.SchemaCoder;
 import org.apache.beam.sdk.transforms.windowing.GlobalWindow;
 import 
org.apache.beam.sdk.transforms.windowing.IntervalWindow.IntervalWindowCoder;
 import org.apache.beam.sdk.util.ShardedKey;
@@ -71,6 +74,7 @@ public class ModelCoderRegistrar implements 
CoderTranslatorRegistrar {
               ModelCoders.PARAM_WINDOWED_VALUE_CODER_URN)
           .put(DoubleCoder.class, ModelCoders.DOUBLE_CODER_URN)
           .put(RowCoder.class, ModelCoders.ROW_CODER_URN)
+          .put(SchemaCoder.class, ModelCoders.SCHEMA_CODER_URN)
           .put(ShardedKey.Coder.class, ModelCoders.SHARDED_KEY_CODER_URN)
           .put(TimestampPrefixingWindowCoder.class, 
ModelCoders.CUSTOM_WINDOW_CODER_URN)
           .put(NullableCoder.class, ModelCoders.NULLABLE_CODER_URN)
@@ -96,6 +100,7 @@ public class ModelCoderRegistrar implements 
CoderTranslatorRegistrar {
                   CoderTranslators.paramWindowedValue())
               .put(DoubleCoder.class, 
CoderTranslators.atomic(DoubleCoder.class))
               .put(RowCoder.class, CoderTranslators.row())
+              .put(SchemaCoder.class, CoderTranslators.schema())
               .put(ShardedKey.Coder.class, CoderTranslators.shardedKey())
               .put(TimestampPrefixingWindowCoder.class, 
CoderTranslators.timestampPrefixingWindow())
               .put(NullableCoder.class, CoderTranslators.nullable())
@@ -123,10 +128,6 @@ public class ModelCoderRegistrar implements 
CoderTranslatorRegistrar {
         Coder.class.getSimpleName());
   }
 
-  public static boolean isKnownCoder(Coder<?> coder) {
-    return BEAM_MODEL_CODER_URNS.containsKey(coder.getClass());
-  }
-
   @Override
   public Map<Class<? extends Coder>, String> getCoderURNs() {
     return BEAM_MODEL_CODER_URNS;
@@ -136,4 +137,23 @@ public class ModelCoderRegistrar implements 
CoderTranslatorRegistrar {
   public Map<Class<? extends Coder>, CoderTranslator<? extends Coder>> 
getCoderTranslators() {
     return BEAM_MODEL_CODERS;
   }
+
+  @Override
+  public boolean isKnownCoder(Coder<?> coder, PipelineOptions options) {
+    if (coder.getClass() == SchemaCoder.class
+        && StreamingOptions.updateCompatibilityVersionLessThan(options, 
"2.77")) {
+      return false;
+    }
+    return BEAM_MODEL_CODER_URNS.containsKey(coder.getClass());
+  }
+
+  @Override
+  public CoderTranslator<? extends Coder> getCoderTranslator(Class<? extends 
Coder> coderClass) {
+    return BEAM_MODEL_CODERS.getOrDefault(coderClass, null);
+  }
+
+  @Override
+  public Class<? extends Coder> getCoderForUrn(String coderUrn) {
+    return BEAM_MODEL_CODER_URNS.inverse().getOrDefault(coderUrn, null);
+  }
 }
diff --git 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/ModelCoders.java
 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/ModelCoders.java
index 7b7546aceb6..5059cc1c6b8 100644
--- 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/ModelCoders.java
+++ 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/ModelCoders.java
@@ -61,6 +61,7 @@ public class ModelCoders {
       getUrn(StandardCoders.Enum.PARAM_WINDOWED_VALUE);
 
   public static final String ROW_CODER_URN = getUrn(StandardCoders.Enum.ROW);
+  public static final String SCHEMA_CODER_URN = 
getUrn(StandardCoders.Enum.SCHEMA);
 
   public static final String STATE_BACKED_ITERABLE_CODER_URN =
       "beam:coder:state_backed_iterable:v1";
@@ -90,6 +91,7 @@ public class ModelCoders {
           WINDOWED_VALUE_CODER_URN,
           DOUBLE_CODER_URN,
           ROW_CODER_URN,
+          SCHEMA_CODER_URN,
           PARAM_WINDOWED_VALUE_CODER_URN,
           STATE_BACKED_ITERABLE_CODER_URN,
           SHARDED_KEY_CODER_URN,
diff --git 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/RehydratedComponents.java
 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/RehydratedComponents.java
index f7969621436..64c7898a37b 100644
--- 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/RehydratedComponents.java
+++ 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/RehydratedComponents.java
@@ -189,6 +189,7 @@ public class RehydratedComponents {
         windowingStrategies.asMap(),
         coders.asMap(),
         Collections.emptyMap(),
-        requirements);
+        requirements,
+        pipeline.getOptions());
   }
 }
diff --git 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/SdkComponents.java
 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/SdkComponents.java
index 446697f24a8..6288649aba3 100644
--- 
a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/SdkComponents.java
+++ 
b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/construction/SdkComponents.java
@@ -63,6 +63,7 @@ public class SdkComponents {
   private final BiMap<Environment, String> environmentIds = HashBiMap.create();
   private final BiMap<RunnerApi.Coder, String> coderProtoToId = 
HashBiMap.create();
   private final Set<String> requirements;
+  private final PipelineOptions pipelineOptions;
 
   private final Set<String> reservedIds = new HashSet<>();
 
@@ -71,17 +72,7 @@ public class SdkComponents {
 
   /** Create a new {@link SdkComponents} with no components. */
   public static SdkComponents create() {
-    return new SdkComponents(RunnerApi.Components.getDefaultInstance(), null, 
"");
-  }
-
-  /**
-   * Create new {@link SdkComponents} importing all items from provided {@link 
Components} object.
-   *
-   * <p>WARNING: This action might cause some of duplicate items created.
-   */
-  public static SdkComponents create(
-      RunnerApi.Components components, Collection<String> requirements) {
-    return new SdkComponents(components, requirements, "");
+    return new SdkComponents(RunnerApi.Components.getDefaultInstance(), null, 
"", null);
   }
 
   /*package*/ static SdkComponents create(
@@ -91,8 +82,9 @@ public class SdkComponents {
       Map<String, WindowingStrategy<?, ?>> windowingStrategies,
       Map<String, Coder<?>> coders,
       Map<String, Environment> environments,
-      Collection<String> requirements) {
-    SdkComponents sdkComponents = SdkComponents.create(components, 
requirements);
+      Collection<String> requirements,
+      PipelineOptions pipelineOptions) {
+    SdkComponents sdkComponents = new SdkComponents(components, requirements, 
"", pipelineOptions);
     sdkComponents.transformIds.inverse().putAll(transforms);
     sdkComponents.pCollectionIds.inverse().putAll(pCollections);
     sdkComponents.windowingStrategyIds.inverse().putAll(windowingStrategies);
@@ -103,19 +95,28 @@ public class SdkComponents {
 
   public static SdkComponents create(PipelineOptions options) {
     SdkComponents sdkComponents =
-        new SdkComponents(RunnerApi.Components.getDefaultInstance(), null, "");
+        new SdkComponents(RunnerApi.Components.getDefaultInstance(), null, "", 
options);
     PortablePipelineOptions portablePipelineOptions = 
options.as(PortablePipelineOptions.class);
     sdkComponents.registerEnvironment(
         Environments.createOrGetDefaultEnvironment(portablePipelineOptions));
     return sdkComponents;
   }
 
+  public static SdkComponents create(PipelineOptions options, Environment 
environment) {
+    SdkComponents sdkComponents =
+        new SdkComponents(RunnerApi.Components.getDefaultInstance(), null, "", 
options);
+    sdkComponents.registerEnvironment(environment);
+    return sdkComponents;
+  }
+
   private SdkComponents(
       @Nullable Components components,
       @Nullable Collection<String> requirements,
-      String newIdPrefix) {
+      String newIdPrefix,
+      @Nullable PipelineOptions pipelineOptions) {
     this.newIdPrefix = newIdPrefix;
     this.requirements = new HashSet<>();
+    this.pipelineOptions = pipelineOptions;
 
     if (components == null) {
       if (requirements != null) {
@@ -153,7 +154,7 @@ public class SdkComponents {
    */
   public SdkComponents withNewIdPrefix(String newIdPrefix) {
     SdkComponents sdkComponents =
-        new SdkComponents(componentsBuilder.build(), requirements, 
newIdPrefix);
+        new SdkComponents(componentsBuilder.build(), requirements, 
newIdPrefix, pipelineOptions);
     sdkComponents.transformIds.putAll(transformIds);
     sdkComponents.pCollectionIds.putAll(pCollectionIds);
     sdkComponents.windowingStrategyIds.putAll(windowingStrategyIds);
@@ -174,7 +175,7 @@ public class SdkComponents {
       throws IOException {
     String name = getApplicationName(appliedPTransform);
     // If this transform is present in the components, nothing to do. return 
the existing name.
-    // Otherwise the transform must be translated and added to the components.
+    // Otherwise, the transform must be translated and added to the components.
     if (componentsBuilder.getTransformsOrDefault(name, null) != null) {
       return name;
     }
@@ -375,4 +376,8 @@ public class SdkComponents {
   public Collection<String> requirements() {
     return ImmutableSet.copyOf(requirements);
   }
+
+  public PipelineOptions getPipelineOptions() {
+    return pipelineOptions;
+  }
 }
diff --git 
a/sdks/java/core/src/test/java/org/apache/beam/sdk/util/construction/CoderTranslationTest.java
 
b/sdks/java/core/src/test/java/org/apache/beam/sdk/util/construction/CoderTranslationTest.java
index b8f92ff0053..1ec0a74f5be 100644
--- 
a/sdks/java/core/src/test/java/org/apache/beam/sdk/util/construction/CoderTranslationTest.java
+++ 
b/sdks/java/core/src/test/java/org/apache/beam/sdk/util/construction/CoderTranslationTest.java
@@ -22,6 +22,7 @@ import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItems;
 import static org.hamcrest.Matchers.not;
 
+import com.google.auto.value.AutoValue;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -45,14 +46,20 @@ import org.apache.beam.sdk.coders.SerializableCoder;
 import org.apache.beam.sdk.coders.StringUtf8Coder;
 import org.apache.beam.sdk.coders.TimestampPrefixingWindowCoder;
 import org.apache.beam.sdk.coders.VarLongCoder;
+import org.apache.beam.sdk.schemas.AutoValueSchema;
+import org.apache.beam.sdk.schemas.NoSuchSchemaException;
 import org.apache.beam.sdk.schemas.Schema;
 import org.apache.beam.sdk.schemas.Schema.Field;
 import org.apache.beam.sdk.schemas.Schema.FieldType;
+import org.apache.beam.sdk.schemas.SchemaCoder;
+import org.apache.beam.sdk.schemas.SchemaRegistry;
+import org.apache.beam.sdk.schemas.annotations.DefaultSchema;
 import org.apache.beam.sdk.schemas.logicaltypes.FixedBytes;
 import org.apache.beam.sdk.transforms.windowing.GlobalWindow;
 import 
org.apache.beam.sdk.transforms.windowing.IntervalWindow.IntervalWindowCoder;
 import org.apache.beam.sdk.util.ShardedKey;
 import 
org.apache.beam.sdk.util.construction.CoderTranslation.TranslationContext;
+import org.apache.beam.sdk.values.TypeDescriptor;
 import org.apache.beam.sdk.values.WindowedValues;
 import org.apache.beam.sdk.values.WindowedValues.FullWindowedValueCoder;
 import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableList;
@@ -70,6 +77,34 @@ import org.junit.runners.Parameterized.Parameters;
   "rawtypes", // TODO(https://github.com/apache/beam/issues/20447)
 })
 public class CoderTranslationTest {
+  @AutoValue
+  @DefaultSchema(AutoValueSchema.class)
+  public abstract static class SimpleAutoValue {
+    public abstract String getString();
+
+    public abstract int getInt32();
+
+    public abstract long getInt64();
+
+    public static SimpleAutoValue of(String string, Integer int32, Long int64) 
{
+      return new AutoValue_CoderTranslationTest_SimpleAutoValue(string, int32, 
int64);
+    }
+  }
+
+  private static final SchemaRegistry REGISTRY = 
SchemaRegistry.createDefault();
+
+  private static SchemaCoder schemaCoderFrom(TypeDescriptor typeDescriptor) {
+    try {
+      return SchemaCoder.of(
+          REGISTRY.getSchema(typeDescriptor),
+          typeDescriptor,
+          REGISTRY.getToRowFunction(typeDescriptor),
+          REGISTRY.getFromRowFunction(typeDescriptor));
+    } catch (NoSuchSchemaException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
   private static final Set<Coder<?>> KNOWN_CODERS =
       ImmutableSet.<Coder<?>>builder()
           .add(ByteArrayCoder.of())
@@ -94,6 +129,7 @@ public class CoderTranslationTest {
                       Field.of("array", FieldType.array(FieldType.STRING)),
                       Field.of("map", FieldType.map(FieldType.STRING, 
FieldType.INT32)),
                       Field.of("bar", 
FieldType.logicalType(FixedBytes.of(123))))))
+          .add(schemaCoderFrom(TypeDescriptor.of(SimpleAutoValue.class)))
           .add(ShardedKey.Coder.of(StringUtf8Coder.of()))
           .add(TimestampPrefixingWindowCoder.of(IntervalWindowCoder.of()))
           .add(NullableCoder.of(ByteArrayCoder.of()))
@@ -127,7 +163,7 @@ public class CoderTranslationTest {
     }
 
     @Test
-    public void validateCoderTranslators() {
+    public void validateModelCoderTranslators() {
       assertThat(
           "Every Model Coder must have a Translator",
           new ModelCoderRegistrar().getCoderURNs().keySet(),
diff --git 
a/sdks/java/extensions/avro/src/main/java/org/apache/beam/sdk/extensions/avro/AvroGenericCoderRegistrar.java
 
b/sdks/java/extensions/avro/src/main/java/org/apache/beam/sdk/extensions/avro/AvroGenericCoderRegistrar.java
index 14ab48f6669..8bd18fd8e25 100644
--- 
a/sdks/java/extensions/avro/src/main/java/org/apache/beam/sdk/extensions/avro/AvroGenericCoderRegistrar.java
+++ 
b/sdks/java/extensions/avro/src/main/java/org/apache/beam/sdk/extensions/avro/AvroGenericCoderRegistrar.java
@@ -21,9 +21,11 @@ import com.google.auto.service.AutoService;
 import java.util.Map;
 import org.apache.beam.sdk.coders.Coder;
 import org.apache.beam.sdk.extensions.avro.coders.AvroGenericCoder;
+import org.apache.beam.sdk.options.PipelineOptions;
 import org.apache.beam.sdk.util.construction.CoderTranslator;
 import org.apache.beam.sdk.util.construction.CoderTranslatorRegistrar;
 import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap;
+import org.checkerframework.checker.nullness.qual.Nullable;
 
 /** Coder registrar for AvroGenericCoder. */
 @AutoService(CoderTranslatorRegistrar.class)
@@ -42,4 +44,20 @@ public class AvroGenericCoderRegistrar implements 
CoderTranslatorRegistrar {
   public Map<Class<? extends Coder>, CoderTranslator<? extends Coder>> 
getCoderTranslators() {
     return ImmutableMap.of(AvroGenericCoder.class, new 
AvroGenericCoderTranslator());
   }
+
+  @Override
+  public boolean isKnownCoder(Coder<?> coder, PipelineOptions options) {
+    return coder.getClass() == AvroGenericCoder.class;
+  }
+
+  @Override
+  public @Nullable CoderTranslator<? extends Coder> getCoderTranslator(
+      Class<? extends Coder> coderClass) {
+    return coderClass == AvroGenericCoder.class ? new 
AvroGenericCoderTranslator() : null;
+  }
+
+  @Override
+  public @Nullable Class<? extends Coder> getCoderForUrn(String coderUrn) {
+    return AVRO_CODER_URN.equals(coderUrn) ? AvroGenericCoder.class : null;
+  }
 }
diff --git 
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/StateBackedIterable.java
 
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/StateBackedIterable.java
index ef8d69bc1ec..42a6f8d11c2 100644
--- 
a/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/StateBackedIterable.java
+++ 
b/sdks/java/harness/src/main/java/org/apache/beam/fn/harness/state/StateBackedIterable.java
@@ -38,6 +38,7 @@ import 
org.apache.beam.model.fnexecution.v1.BeamFnApi.StateRequest;
 import org.apache.beam.sdk.coders.IterableLikeCoder;
 import org.apache.beam.sdk.fn.stream.PrefetchableIterable;
 import org.apache.beam.sdk.fn.stream.PrefetchableIterators;
+import org.apache.beam.sdk.options.PipelineOptions;
 import org.apache.beam.sdk.util.BufferedElementCountingOutputStream;
 import org.apache.beam.sdk.util.VarInt;
 import org.apache.beam.sdk.util.common.ElementByteSizeObservableIterable;
@@ -52,6 +53,7 @@ import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Immuta
 import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.ImmutableMap;
 import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.collect.Iterables;
 import 
org.apache.beam.vendor.guava.v32_1_2_jre.com.google.common.io.ByteStreams;
+import org.checkerframework.checker.nullness.qual.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -300,6 +302,26 @@ public class StateBackedIterable<T>
         getCoderTranslators() {
       return ImmutableMap.of(StateBackedIterable.Coder.class, new 
Translator());
     }
+
+    @Override
+    public boolean isKnownCoder(
+        org.apache.beam.sdk.coders.Coder<?> coder, PipelineOptions options) {
+      return coder.getClass() == StateBackedIterable.Coder.class;
+    }
+
+    @Override
+    public @Nullable CoderTranslator<? extends 
org.apache.beam.sdk.coders.Coder> getCoderTranslator(
+        Class<? extends org.apache.beam.sdk.coders.Coder> coderClass) {
+      return coderClass == StateBackedIterable.Coder.class ? new Translator() 
: null;
+    }
+
+    @Override
+    public @Nullable Class<? extends org.apache.beam.sdk.coders.Coder> 
getCoderForUrn(
+        String coderUrn) {
+      return STATE_BACKED_ITERABLE_CODER_URN.equals(coderUrn)
+          ? StateBackedIterable.Coder.class
+          : null;
+    }
   }
 
   /**
diff --git 
a/sdks/python/apache_beam/runners/portability/fn_api_runner/execution.py 
b/sdks/python/apache_beam/runners/portability/fn_api_runner/execution.py
index d2f0f97a8a3..fe2421f8ee8 100644
--- a/sdks/python/apache_beam/runners/portability/fn_api_runner/execution.py
+++ b/sdks/python/apache_beam/runners/portability/fn_api_runner/execution.py
@@ -780,9 +780,9 @@ class FnApiRunnerExecutionContext(object):
         if transform.spec.urn == bundle_processor.DATA_INPUT_URN:
           coder_id = self.data_channel_coders[only_element(
               transform.outputs.values())]
-          coder = self.pipeline_context.coders[self.safe_coders.get(
-              coder_id, coder_id)]
           if transform.spec.payload == translations.IMPULSE_BUFFER:
+            coder = self.pipeline_context.coders[self.safe_coders.get(
+                coder_id, coder_id)]
             data_input[transform.unique_name] = ListBuffer(coder.get_impl())
             data_input[transform.unique_name].append(ENCODED_IMPULSE_VALUE)
           else:
diff --git 
a/sdks/python/apache_beam/runners/portability/fn_api_runner/translations.py 
b/sdks/python/apache_beam/runners/portability/fn_api_runner/translations.py
index 7261a815f7e..c77b10cd855 100644
--- a/sdks/python/apache_beam/runners/portability/fn_api_runner/translations.py
+++ b/sdks/python/apache_beam/runners/portability/fn_api_runner/translations.py
@@ -387,8 +387,8 @@ class TransformContext(object):
   _COMMON_CODER_URNS = set(
       value.urn for (key, value) in common_urns.coders.__dict__.items()
       if not key.startswith('_')
-      # Length prefix Rows rather than re-coding them.
-  ) - set([common_urns.coders.ROW.urn])
+      # Length prefix Rows and Schemas rather than re-coding them.
+  ) - set([common_urns.coders.ROW.urn, common_urns.coders.SCHEMA.urn])
 
   _REQUIRED_CODER_URNS = set([
       common_urns.coders.WINDOWED_VALUE.urn,

Reply via email to