[ObjC] Add tests for proto3 optional behaviors.
diff --git a/objectivec/DevTools/compile_testing_protos.sh b/objectivec/DevTools/compile_testing_protos.sh
index dc1d6d2..d04c5c5 100755
--- a/objectivec/DevTools/compile_testing_protos.sh
+++ b/objectivec/DevTools/compile_testing_protos.sh
@@ -158,6 +158,7 @@
     --objc_out="${OUTPUT_DIR}/google/protobuf" \
     --proto_path=src/google/protobuf/          \
     --proto_path=src                           \
+    --experimental_allow_proto3_optional       \
     "$@"
 }
 
diff --git a/objectivec/Tests/GPBMessageTests+Runtime.m b/objectivec/Tests/GPBMessageTests+Runtime.m
index aa5b0db..1dac797 100644
--- a/objectivec/Tests/GPBMessageTests+Runtime.m
+++ b/objectivec/Tests/GPBMessageTests+Runtime.m
@@ -270,6 +270,23 @@
                    @"field: %@", name);
   }
 
+  // Single Optional fields
+  //  - has*/setHas* thanks to the optional keyword in proto3, they exist
+  //    for primitive types.
+  //  - has*/setHas* valid for Message.
+
+  for (NSString *name in names) {
+    // build the selector, i.e. - hasOptionalInt32/setHasOptionalInt32:
+    SEL hasSel = NSSelectorFromString(
+        [NSString stringWithFormat:@"hasOptional%@", name]);
+    SEL setHasSel = NSSelectorFromString(
+        [NSString stringWithFormat:@"setHasOptional%@:", name]);
+    XCTAssertTrue([Message3Optional instancesRespondToSelector:hasSel], @"field: %@",
+                  name);
+    XCTAssertTrue([Message3Optional instancesRespondToSelector:setHasSel],
+                  @"field: %@", name);
+  }
+
   // map<> fields
   //  - no has*/setHas*
   //  - *Count
@@ -1002,6 +1019,249 @@
 //%PDDM-EXPAND-END PROTO3_TEST_HAS_FIELDS()
 }
 
+- (void)testProto3SingleOptionalFieldHasBehavior {
+  //
+  // Setting to any value including the default (0) should result in true.
+  //
+
+//%PDDM-DEFINE PROTO3_TEST_OPTIONAL_HAS_FIELD(FIELD, NON_ZERO_VALUE, ZERO_VALUE)
+//%  {  // optional##FIELD
+//%    Message3Optional *msg = [[Message3Optional alloc] init];
+//%    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_Optional##FIELD));
+//%    msg.optional##FIELD = NON_ZERO_VALUE;
+//%    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_Optional##FIELD));
+//%    msg.hasOptional##FIELD = NO;
+//%    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_Optional##FIELD));
+//%    msg.optional##FIELD = ZERO_VALUE;
+//%    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_Optional##FIELD));
+//%    [msg release];
+//%  }
+//%
+//%PDDM-DEFINE PROTO3_TEST_OPTIONAL_HAS_FIELDS()
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Int32, 1, 0)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Int64, 1, 0)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Uint32, 1, 0)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Uint64, 1, 0)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Sint32, 1, 0)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Sint64, 1, 0)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Fixed32, 1, 0)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Fixed64, 1, 0)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Sfixed32, 1, 0)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Sfixed64, 1, 0)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Float, 1.0f, 0.0f)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Double, 1.0, 0.0)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Bool, YES, NO)
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(String, @"foo", @"")
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Bytes, [@"foo" dataUsingEncoding:NSUTF8StringEncoding], [NSData data])
+//%  //
+//%  // Test doesn't apply to optionalMessage (no groups in proto3).
+//%  //
+//%
+//%PROTO3_TEST_OPTIONAL_HAS_FIELD(Enum, Message3Optional_Enum_Bar, Message3Optional_Enum_Foo)
+//%PDDM-EXPAND PROTO3_TEST_OPTIONAL_HAS_FIELDS()
+// This block of code is generated, do not edit it directly.
+// clang-format off
+
+  {  // optionalInt32
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalInt32));
+    msg.optionalInt32 = 1;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalInt32));
+    msg.hasOptionalInt32 = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalInt32));
+    msg.optionalInt32 = 0;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalInt32));
+    [msg release];
+  }
+
+  {  // optionalInt64
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalInt64));
+    msg.optionalInt64 = 1;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalInt64));
+    msg.hasOptionalInt64 = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalInt64));
+    msg.optionalInt64 = 0;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalInt64));
+    [msg release];
+  }
+
+  {  // optionalUint32
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalUint32));
+    msg.optionalUint32 = 1;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalUint32));
+    msg.hasOptionalUint32 = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalUint32));
+    msg.optionalUint32 = 0;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalUint32));
+    [msg release];
+  }
+
+  {  // optionalUint64
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalUint64));
+    msg.optionalUint64 = 1;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalUint64));
+    msg.hasOptionalUint64 = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalUint64));
+    msg.optionalUint64 = 0;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalUint64));
+    [msg release];
+  }
+
+  {  // optionalSint32
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSint32));
+    msg.optionalSint32 = 1;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSint32));
+    msg.hasOptionalSint32 = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSint32));
+    msg.optionalSint32 = 0;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSint32));
+    [msg release];
+  }
+
+  {  // optionalSint64
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSint64));
+    msg.optionalSint64 = 1;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSint64));
+    msg.hasOptionalSint64 = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSint64));
+    msg.optionalSint64 = 0;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSint64));
+    [msg release];
+  }
+
+  {  // optionalFixed32
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalFixed32));
+    msg.optionalFixed32 = 1;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalFixed32));
+    msg.hasOptionalFixed32 = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalFixed32));
+    msg.optionalFixed32 = 0;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalFixed32));
+    [msg release];
+  }
+
+  {  // optionalFixed64
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalFixed64));
+    msg.optionalFixed64 = 1;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalFixed64));
+    msg.hasOptionalFixed64 = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalFixed64));
+    msg.optionalFixed64 = 0;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalFixed64));
+    [msg release];
+  }
+
+  {  // optionalSfixed32
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSfixed32));
+    msg.optionalSfixed32 = 1;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSfixed32));
+    msg.hasOptionalSfixed32 = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSfixed32));
+    msg.optionalSfixed32 = 0;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSfixed32));
+    [msg release];
+  }
+
+  {  // optionalSfixed64
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSfixed64));
+    msg.optionalSfixed64 = 1;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSfixed64));
+    msg.hasOptionalSfixed64 = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSfixed64));
+    msg.optionalSfixed64 = 0;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalSfixed64));
+    [msg release];
+  }
+
+  {  // optionalFloat
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalFloat));
+    msg.optionalFloat = 1.0f;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalFloat));
+    msg.hasOptionalFloat = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalFloat));
+    msg.optionalFloat = 0.0f;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalFloat));
+    [msg release];
+  }
+
+  {  // optionalDouble
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalDouble));
+    msg.optionalDouble = 1.0;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalDouble));
+    msg.hasOptionalDouble = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalDouble));
+    msg.optionalDouble = 0.0;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalDouble));
+    [msg release];
+  }
+
+  {  // optionalBool
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalBool));
+    msg.optionalBool = YES;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalBool));
+    msg.hasOptionalBool = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalBool));
+    msg.optionalBool = NO;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalBool));
+    [msg release];
+  }
+
+  {  // optionalString
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalString));
+    msg.optionalString = @"foo";
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalString));
+    msg.hasOptionalString = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalString));
+    msg.optionalString = @"";
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalString));
+    [msg release];
+  }
+
+  {  // optionalBytes
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalBytes));
+    msg.optionalBytes = [@"foo" dataUsingEncoding:NSUTF8StringEncoding];
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalBytes));
+    msg.hasOptionalBytes = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalBytes));
+    msg.optionalBytes = [NSData data];
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalBytes));
+    [msg release];
+  }
+
+  //
+  // Test doesn't apply to optionalMessage (no groups in proto3).
+  //
+
+  {  // optionalEnum
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalEnum));
+    msg.optionalEnum = Message3Optional_Enum_Bar;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalEnum));
+    msg.hasOptionalEnum = NO;
+    XCTAssertFalse(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalEnum));
+    msg.optionalEnum = Message3Optional_Enum_Foo;
+    XCTAssertTrue(GPBMessageHasFieldNumberSet(msg, Message3Optional_FieldNumber_OptionalEnum));
+    [msg release];
+  }
+
+// clang-format on
+//%PDDM-EXPAND-END PROTO3_TEST_OPTIONAL_HAS_FIELDS()
+}
+
 - (void)testAccessingProto2UnknownEnumValues {
   Message2 *msg = [[Message2 alloc] init];
 
diff --git a/objectivec/Tests/GPBMessageTests+Serialization.m b/objectivec/Tests/GPBMessageTests+Serialization.m
index ef6e589..6f20797 100644
--- a/objectivec/Tests/GPBMessageTests+Serialization.m
+++ b/objectivec/Tests/GPBMessageTests+Serialization.m
@@ -109,6 +109,317 @@
   [msg release];
 }
 
+- (void)testProto3SerializationHandlingOptionals {
+  //
+  // Proto3 optionals should be just like proto2, zero values also get serialized.
+  //
+
+//%PDDM-DEFINE PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(FIELD, ZERO_VALUE, EXPECTED_LEN)
+//%  {  // optional##FIELD
+//%    Message3Optional *msg = [[Message3Optional alloc] init];
+//%    NSData *data = [msg data];
+//%    XCTAssertEqual([data length], 0U);
+//%    msg.optional##FIELD = ZERO_VALUE;
+//%    data = [msg data];
+//%    XCTAssertEqual(data.length, EXPECTED_LEN);
+//%    NSError *err = nil;
+//%    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+//%    XCTAssertNotNil(msg2);
+//%    XCTAssertNil(err);
+//%    XCTAssertTrue(msg2.hasOptional##FIELD);
+//%    XCTAssertEqualObjects(msg, msg2);
+//%    [msg release];
+//%  }
+//%
+//%PDDM-DEFINE PROTO3_TEST_SERIALIZE_OPTIONAL_FIELDS()
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Int32, 0, 2)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Int64, 0, 2)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Uint32, 0, 2)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Uint64, 0, 2)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Sint32, 0, 2)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Sint64, 0, 2)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Fixed32, 0, 5)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Fixed64, 0, 9)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Sfixed32, 0, 5)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Sfixed64, 0, 9)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Float, 0.0f, 5)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Double, 0.0, 9)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Bool, NO, 2)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(String, @"", 2)
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Bytes, [NSData data], 2)
+//%  //
+//%  // Test doesn't apply to optionalMessage (no groups in proto3).
+//%  //
+//%
+//%PROTO3_TEST_SERIALIZE_OPTIONAL_FIELD(Enum, Message3Optional_Enum_Foo, 3)
+//%PDDM-EXPAND PROTO3_TEST_SERIALIZE_OPTIONAL_FIELDS()
+// This block of code is generated, do not edit it directly.
+// clang-format off
+
+  {  // optionalInt32
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalInt32 = 0;
+    data = [msg data];
+    XCTAssertEqual(data.length, 2);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalInt32);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalInt64
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalInt64 = 0;
+    data = [msg data];
+    XCTAssertEqual(data.length, 2);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalInt64);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalUint32
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalUint32 = 0;
+    data = [msg data];
+    XCTAssertEqual(data.length, 2);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalUint32);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalUint64
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalUint64 = 0;
+    data = [msg data];
+    XCTAssertEqual(data.length, 2);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalUint64);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalSint32
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalSint32 = 0;
+    data = [msg data];
+    XCTAssertEqual(data.length, 2);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalSint32);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalSint64
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalSint64 = 0;
+    data = [msg data];
+    XCTAssertEqual(data.length, 2);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalSint64);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalFixed32
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalFixed32 = 0;
+    data = [msg data];
+    XCTAssertEqual(data.length, 5);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalFixed32);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalFixed64
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalFixed64 = 0;
+    data = [msg data];
+    XCTAssertEqual(data.length, 9);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalFixed64);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalSfixed32
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalSfixed32 = 0;
+    data = [msg data];
+    XCTAssertEqual(data.length, 5);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalSfixed32);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalSfixed64
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalSfixed64 = 0;
+    data = [msg data];
+    XCTAssertEqual(data.length, 9);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalSfixed64);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalFloat
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalFloat = 0.0f;
+    data = [msg data];
+    XCTAssertEqual(data.length, 5);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalFloat);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalDouble
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalDouble = 0.0;
+    data = [msg data];
+    XCTAssertEqual(data.length, 9);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalDouble);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalBool
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalBool = NO;
+    data = [msg data];
+    XCTAssertEqual(data.length, 2);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalBool);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalString
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalString = @"";
+    data = [msg data];
+    XCTAssertEqual(data.length, 2);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalString);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  {  // optionalBytes
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalBytes = [NSData data];
+    data = [msg data];
+    XCTAssertEqual(data.length, 2);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalBytes);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+  //
+  // Test doesn't apply to optionalMessage (no groups in proto3).
+  //
+
+  {  // optionalEnum
+    Message3Optional *msg = [[Message3Optional alloc] init];
+    NSData *data = [msg data];
+    XCTAssertEqual([data length], 0U);
+    msg.optionalEnum = Message3Optional_Enum_Foo;
+    data = [msg data];
+    XCTAssertEqual(data.length, 3);
+    NSError *err = nil;
+    Message3Optional *msg2 = [Message3Optional parseFromData:data error:&err];
+    XCTAssertNotNil(msg2);
+    XCTAssertNil(err);
+    XCTAssertTrue(msg2.hasOptionalEnum);
+    XCTAssertEqualObjects(msg, msg2);
+    [msg release];
+  }
+
+// clang-format on
+//%PDDM-EXPAND-END PROTO3_TEST_SERIALIZE_OPTIONAL_FIELDS()
+}
+
 - (void)testProto2UnknownEnumToUnknownField {
   Message3 *orig = [[Message3 alloc] init];
 
diff --git a/objectivec/Tests/unittest_runtime_proto3.proto b/objectivec/Tests/unittest_runtime_proto3.proto
index ad2e362..c2ee5fb 100644
--- a/objectivec/Tests/unittest_runtime_proto3.proto
+++ b/objectivec/Tests/unittest_runtime_proto3.proto
@@ -119,3 +119,31 @@
   map<int32   , Enum    > map_int32_enum        = 87;
   map<int32   , Message3> map_int32_message     = 88;
 }
+
+message Message3Optional {
+  enum Enum {
+    FOO = 0;
+    BAR = 1;
+    BAZ = 2;
+    EXTRA_3 = 30;
+  }
+
+  optional    int32 optional_int32    =  1;
+  optional    int64 optional_int64    =  2;
+  optional   uint32 optional_uint32   =  3;
+  optional   uint64 optional_uint64   =  4;
+  optional   sint32 optional_sint32   =  5;
+  optional   sint64 optional_sint64   =  6;
+  optional  fixed32 optional_fixed32  =  7;
+  optional  fixed64 optional_fixed64  =  8;
+  optional sfixed32 optional_sfixed32 =  9;
+  optional sfixed64 optional_sfixed64 = 10;
+  optional    float optional_float    = 11;
+  optional   double optional_double   = 12;
+  optional     bool optional_bool     = 13;
+  optional   string optional_string   = 14;
+  optional    bytes optional_bytes    = 15;
+  // No 'group' in proto3.
+  optional Message3  optional_message = 18;
+  optional Enum         optional_enum = 19;
+}