Skip to content

Commit 79ae776

Browse files
authored
Fix PipelineOptions deserialization NPE (#38531)
1 parent 55eb624 commit 79ae776

2 files changed

Lines changed: 21 additions & 9 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"comment": "Modify this file in a trivial way to cause this test suite to run",
3+
"revision": 1
4+
}

sdks/java/core/src/main/java/org/apache/beam/sdk/options/PipelineOptionsFactory.java

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -502,13 +502,6 @@ Class<T> getProxyClass() {
502502
new ObjectMapper()
503503
.registerModules(ObjectMapper.findModules(ReflectHelpers.findClassLoader()));
504504

505-
private static final DefaultDeserializationContext DESERIALIZATION_CONTEXT =
506-
new DefaultDeserializationContext.Impl(MAPPER.getDeserializationContext().getFactory())
507-
.createInstance(
508-
MAPPER.getDeserializationConfig(),
509-
new TokenBuffer(MAPPER, false).asParser(),
510-
new InjectableValues.Std());
511-
512505
static final DefaultSerializerProvider SERIALIZER_PROVIDER =
513506
new DefaultSerializerProvider.Impl()
514507
.createInstance(MAPPER.getSerializationConfig(), MAPPER.getSerializerFactory());
@@ -1733,7 +1726,12 @@ private static JsonDeserializer<Object> computeDeserializerForMethod(Method meth
17331726
BeanProperty prop = createBeanProperty(method);
17341727
AnnotatedMember annotatedMethod = prop.getMember();
17351728

1736-
DefaultDeserializationContext context = DESERIALIZATION_CONTEXT.copy();
1729+
// Initialize a new context that is properly associated with a dummy parser.
1730+
// Using copy() here would leave the context's transient parser field as null,
1731+
// causing NullPointerExceptions in Jackson 2.14+ when deserializers try to
1732+
// query the parser for format constraints or coercion validations.
1733+
JsonParser dummyParser = new TokenBuffer(MAPPER, false).asParser();
1734+
DefaultDeserializationContext context = createDeserializationContext(dummyParser);
17371735
Object maybeDeserializerClass =
17381736
context.getAnnotationIntrospector().findDeserializer(annotatedMethod);
17391737

@@ -1756,6 +1754,11 @@ private static JsonDeserializer<Object> computeDeserializerForMethod(Method meth
17561754
}
17571755
}
17581756

1757+
private static DefaultDeserializationContext createDeserializationContext(JsonParser parser) {
1758+
return ((DefaultDeserializationContext) MAPPER.getDeserializationContext())
1759+
.createInstance(MAPPER.getDeserializationConfig(), parser, new InjectableValues.Std());
1760+
}
1761+
17591762
private static Optional<JsonSerializer<Object>> computeCustomSerializerForMethod(Method method) {
17601763
try {
17611764
BeanProperty prop = createBeanProperty(method);
@@ -1805,7 +1808,12 @@ static Object deserializeNode(JsonNode node, Method method) throws IOException {
18051808
parser.nextToken();
18061809

18071810
JsonDeserializer<Object> jsonDeserializer = getDeserializerForMethod(method);
1808-
return jsonDeserializer.deserialize(parser, DESERIALIZATION_CONTEXT.copy());
1811+
// Create a fresh context that is correctly associated with the active parser.
1812+
// Using copy() here would leave the context's transient parser field as null,
1813+
// causing NullPointerExceptions in Jackson 2.14+ deserializers during coercion
1814+
// validations and length checks.
1815+
DefaultDeserializationContext context = createDeserializationContext(parser);
1816+
return jsonDeserializer.deserialize(parser, context);
18091817
}
18101818

18111819
/**

0 commit comments

Comments
 (0)