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

lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new cfcff49d89 GH-35352: [Java] Fix issues with "semi complex" types. 
(#35353)
cfcff49d89 is described below

commit cfcff49d89df220b6ec01bc33a04bbd3421f2f0d
Author: henrymai <[email protected]>
AuthorDate: Thu May 11 12:20:21 2023 -0400

    GH-35352: [Java] Fix issues with "semi complex" types. (#35353)
    
    ### Rationale for this change
    
    "semi complex" types like `TimeStamp*TZ`, `Duration`, and `FixedSizeBinary`
    were missing implementations in `UnionListWriter`, `UnionVector`,
    `UnionReader` and other associated classes.
    
    This patch adds these missing methods so that these types can now be
    written to things like `ListVector`s, whereas before it would throw an
    exception because the methods were just not implemented.
    
    For example, without this patch, one of the new tests added would fail:
    ```
        TestListVector.testWriterGetTimestampMilliTZField:913 ? IllegalArgument 
You tried to write a TimeStampMilliTZ type when you are using a ValueWriter of 
type UnionListWriter.
    ```
    
    There are also fixes for get and set methods for holders for the respective
    `*Vector`s classes for these types:
      - The get methods did not set fields like 
`TimeStampMilliTZHolder.timezone`,
        `DurationHolder.unit`, `FixedSizeBinaryHolder.byteWidth`.
    
      - The set methods did not all validate that those fields matched what
        the vector's `ArrowType` was set to. For example 
`TimeStampMilliTZHolder.timezone` should
        match `ArrowType.Timestamp.timezone` on the vector and should throw if 
it doesn't.
        Otherwise users would never get a signal that there is anything wrong 
with their code writing
        these holders with mismatching values.
    
    This patch additionally marks some of the existing interfaces for writing 
these
    "semi complex" types as deprecated, because they do not actually provide 
enough
    context to properly write these parameterized `ArrowTypes`. Instead, users 
should
    use the write methods that take `*Holders` that do provide enough context 
for the
    writers to properly write these types. Also note that the equivalent write
    methods for `Decimal` have already also been marked as deprecated, for the 
same
    reasoning.
    
    ### What changes are included in this PR?
    
    - Various template changes. See this gist for a diff of the generated 
files: https://gist.github.com/henrymai/03b0f5a4165cd9822aa797c89bbd74e9
       - Note that the diff for the ComplexWriter.java generated ones is not 
included since that change is easy to see from just the template.
    
    - Additional tests to verify that this is fixed.
    
    ### Are these changes tested?
    
    Yes, added unit tests and they pass.
    
    ### Are there any user-facing changes?
    
    Yes, users will now be able to write to types like TimeStamp*TZ, Duration, 
and FixedSizeBinary on ListVector, MapVector, StructVector due to added 
implementations for these types on UnionListVector, UnionWriter.
    
    This also marks the non-holder write interfaces for these types as `@ 
Deprecated`.
    The interfaces themselves have not been removed, so this part is not a 
breaking change.
    Also as mentioned above, this follows in the footsteps of what `Decimal*` 
has already done, where they marked the non holder write interfaces as `@ 
Deprecated`.
    
    Additionally another user visible change is that when reading these types 
into a holder all of the fields are now populated. For example 
`TimeStamp*TZHolder.timezone` is now populated when it was not before.
    
    **This PR includes breaking changes to public APIs.**
    
    We now throw an exception when some of the holder fields that correspond to 
the Vector's ArrowType parameters do not match.
    This is a breaking change because previously this would silently accept the 
writes but was actually incorrect, since the element written does not 
correspond to the right ArrowType variant due to mismatching parameters (like 
ArrowType.Timestamp.unit, ArrowType.Duration.unit, and 
ArrowType.FixedSizeBinary.byteWidth).
    
    * Closes: #35352
    
    Authored-by: Henry Mai <[email protected]>
    Signed-off-by: David Li <[email protected]>
---
 .../templates/AbstractPromotableFieldWriter.java   |  83 +++++++-
 .../src/main/codegen/templates/ComplexWriters.java |  21 +-
 .../src/main/codegen/templates/StructWriters.java  |  19 ++
 .../main/codegen/templates/UnionListWriter.java    | 128 ++++-------
 .../src/main/codegen/templates/UnionReader.java    |   9 +-
 .../src/main/codegen/templates/UnionVector.java    |  64 ++++--
 .../src/main/codegen/templates/UnionWriter.java    |  87 ++++++--
 .../org/apache/arrow/vector/DurationVector.java    |   8 +
 .../apache/arrow/vector/FixedSizeBinaryVector.java |  10 +-
 .../arrow/vector/TimeStampMicroTZVector.java       |   8 +
 .../arrow/vector/TimeStampMilliTZVector.java       |   8 +
 .../apache/arrow/vector/TimeStampNanoTZVector.java |   8 +
 .../apache/arrow/vector/TimeStampSecTZVector.java  |   8 +
 .../vector/complex/impl/PromotableWriter.java      |  12 +-
 .../arrow/vector/TestFixedSizeBinaryVector.java    |  16 +-
 .../org/apache/arrow/vector/TestListVector.java    | 144 +++++++++++++
 .../org/apache/arrow/vector/TestUnionVector.java   |  23 ++
 .../vector/complex/impl/TestPromotableWriter.java  | 234 ++++++++++++++++++++-
 .../vector/complex/writer/TestComplexWriter.java   | 168 ++++++++++++++-
 19 files changed, 909 insertions(+), 149 deletions(-)

diff --git 
a/java/vector/src/main/codegen/templates/AbstractPromotableFieldWriter.java 
b/java/vector/src/main/codegen/templates/AbstractPromotableFieldWriter.java
index 264e850218..2f963a9df0 100644
--- a/java/vector/src/main/codegen/templates/AbstractPromotableFieldWriter.java
+++ b/java/vector/src/main/codegen/templates/AbstractPromotableFieldWriter.java
@@ -25,6 +25,10 @@ package org.apache.arrow.vector.complex.impl;
 
 <#include "/@includes/vv_imports.ftl" />
 
+<#function is_timestamp_tz type>
+  <#return type?starts_with("TimeStamp") && type?ends_with("TZ")>
+</#function>
+
 /*
  * A FieldWriter which delegates calls to another FieldWriter. The delegate 
FieldWriter can be promoted to a new type
  * when necessary. Classes that extend this class are responsible for handling 
promotion.
@@ -105,17 +109,7 @@ abstract class AbstractPromotableFieldWriter extends 
AbstractFieldWriter {
 
   <#list vv.types as type><#list type.minor as minor><#assign name = 
minor.class?cap_first />
     <#assign fields = minor.fields!type.fields />
-  <#if minor.class != "Decimal" && minor.class != "Decimal256">
-  @Override
-  public void write(${name}Holder holder) {
-    getWriter(MinorType.${name?upper_case}).write(holder);
-  }
-
-  public void write${minor.class}(<#list fields as field>${field.type} 
${field.name}<#if field_has_next>, </#if></#list>) {
-    getWriter(MinorType.${name?upper_case}).write${minor.class}(<#list fields 
as field>${field.name}<#if field_has_next>, </#if></#list>);
-  }
-
-  <#elseif minor.class == "Decimal">
+  <#if minor.class == "Decimal">
   @Override
   public void write(DecimalHolder holder) {
     getWriter(MinorType.DECIMAL).write(holder);
@@ -156,8 +150,75 @@ abstract class AbstractPromotableFieldWriter extends 
AbstractFieldWriter {
   public void writeBigEndianBytesToDecimal256(byte[] value) {
     getWriter(MinorType.DECIMAL256).writeBigEndianBytesToDecimal256(value);
   }
+  <#elseif is_timestamp_tz(minor.class)>
+  @Override
+  public void write(${name}Holder holder) {
+    ArrowType.Timestamp arrowTypeWithoutTz = (ArrowType.Timestamp) 
MinorType.${name?upper_case?remove_ending("TZ")}.getType();
+    // Take the holder.timezone similar to how 
PromotableWriter.java:write(DecimalHolder) takes the scale from the holder.
+    ArrowType.Timestamp arrowType = new 
ArrowType.Timestamp(arrowTypeWithoutTz.getUnit(), holder.timezone);
+    getWriter(MinorType.${name?upper_case}, arrowType).write(holder);
+  }
 
+  /**
+   * @deprecated
+   * The holder version should be used instead otherwise the timezone will 
default to UTC.
+   * @see #write(${name}Holder)
+   */
+  @Deprecated
+  @Override
+  public void write${minor.class}(<#list fields as field>${field.type} 
${field.name}<#if field_has_next>, </#if></#list>) {
+    ArrowType.Timestamp arrowTypeWithoutTz = (ArrowType.Timestamp) 
MinorType.${name?upper_case?remove_ending("TZ")}.getType();
+    // Assumes UTC if no timezone is provided
+    ArrowType.Timestamp arrowType = new 
ArrowType.Timestamp(arrowTypeWithoutTz.getUnit(), "UTC");
+    getWriter(MinorType.${name?upper_case}, 
arrowType).write${minor.class}(<#list fields as field>${field.name}<#if 
field_has_next>, </#if></#list>);
+  }
+  <#elseif minor.class == "Duration">
+  @Override
+  public void write(${name}Holder holder) {
+    ArrowType.Duration arrowType = new ArrowType.Duration(holder.unit);
+    getWriter(MinorType.${name?upper_case}, arrowType).write(holder);
+  }
 
+  /**
+   * @deprecated
+   * If you experience errors with using this version of the method, switch to 
the holder version.
+   * The errors occur when using an untyped or unioned PromotableWriter, 
because this version of the
+   * method does not have enough information to infer the ArrowType.
+   * @see #write(${name}Holder)
+   */
+  @Deprecated
+  @Override
+  public void write${minor.class}(<#list fields as field>${field.type} 
${field.name}<#if field_has_next>, </#if></#list>) {
+    getWriter(MinorType.${name?upper_case}).write${minor.class}(<#list fields 
as field>${field.name}<#if field_has_next>, </#if></#list>);
+  }
+  <#elseif minor.class == "FixedSizeBinary">
+  @Override
+  public void write(${name}Holder holder) {
+    ArrowType.FixedSizeBinary arrowType = new 
ArrowType.FixedSizeBinary(holder.byteWidth);
+    getWriter(MinorType.${name?upper_case}, arrowType).write(holder);
+  }
+
+  /**
+   * @deprecated
+   * If you experience errors with using this version of the method, switch to 
the holder version.
+   * The errors occur when using an untyped or unioned PromotableWriter, 
because this version of the
+   * method does not have enough information to infer the ArrowType.
+   * @see #write(${name}Holder)
+   */
+  @Deprecated
+  @Override
+  public void write${minor.class}(<#list fields as field>${field.type} 
${field.name}<#if field_has_next>, </#if></#list>) {
+    getWriter(MinorType.${name?upper_case}).write${minor.class}(<#list fields 
as field>${field.name}<#if field_has_next>, </#if></#list>);
+  }
+  <#else>
+  @Override
+  public void write(${name}Holder holder) {
+    getWriter(MinorType.${name?upper_case}).write(holder);
+  }
+
+  public void write${minor.class}(<#list fields as field>${field.type} 
${field.name}<#if field_has_next>, </#if></#list>) {
+    getWriter(MinorType.${name?upper_case}).write${minor.class}(<#list fields 
as field>${field.name}<#if field_has_next>, </#if></#list>);
+  }
   </#if>
 
   </#list></#list>
diff --git a/java/vector/src/main/codegen/templates/ComplexWriters.java 
b/java/vector/src/main/codegen/templates/ComplexWriters.java
index 0381e5559e..0b1e321afb 100644
--- a/java/vector/src/main/codegen/templates/ComplexWriters.java
+++ b/java/vector/src/main/codegen/templates/ComplexWriters.java
@@ -32,6 +32,10 @@ package org.apache.arrow.vector.complex.impl;
 
 <#include "/@includes/vv_imports.ftl" />
 
+<#function is_timestamp_tz type>
+  <#return type?starts_with("TimeStamp") && type?ends_with("TZ")>
+</#function>
+
 /*
  * This class is generated using FreeMarker on the ${.template_name} template.
  */
@@ -191,7 +195,15 @@ package org.apache.arrow.vector.complex.writer;
 public interface ${eName}Writer extends BaseWriter {
   public void write(${minor.class}Holder h);
 
-  <#if minor.class?starts_with("Decimal")>@Deprecated</#if>
+<#if minor.class?starts_with("Decimal") || is_timestamp_tz(minor.class) || 
minor.class == "Duration" || minor.class == "FixedSizeBinary">
+  /**
+   * @deprecated
+   * The holder version should be used instead because the plain value version 
does not contain enough information
+   * to fully specify this field type.
+   * @see #write(${minor.class}Holder)
+   */
+  @Deprecated
+</#if>
   public void write${minor.class}(<#list fields as field>${field.type} 
${field.name}<#if field_has_next>, </#if></#list>);
 <#if minor.class?starts_with("Decimal")>
 
@@ -201,6 +213,13 @@ public interface ${eName}Writer extends BaseWriter {
 
   public void writeBigEndianBytesTo${minor.class}(byte[] value, ArrowType 
arrowType);
 
+  /**
+   * @deprecated
+   * Use either the version that additionally takes in an ArrowType or use the 
holder version.
+   * This version does not contain enough information to fully specify this 
field type.
+   * @see #writeBigEndianBytesTo${minor.class}(byte[], ArrowType)
+   * @see #write(${minor.class}Holder)
+   */
   @Deprecated
   public void writeBigEndianBytesTo${minor.class}(byte[] value);
 </#if>
diff --git a/java/vector/src/main/codegen/templates/StructWriters.java 
b/java/vector/src/main/codegen/templates/StructWriters.java
index 69693c6301..84e5d8113b 100644
--- a/java/vector/src/main/codegen/templates/StructWriters.java
+++ b/java/vector/src/main/codegen/templates/StructWriters.java
@@ -38,6 +38,10 @@ import org.apache.arrow.vector.AllocationHelper;
 import org.apache.arrow.vector.complex.reader.FieldReader;
 import org.apache.arrow.vector.complex.writer.FieldWriter;
 
+<#function is_timestamp_tz type>
+  <#return type?starts_with("TimeStamp") && type?ends_with("TZ")>
+</#function>
+
 /*
  * This class is generated using FreeMarker and the ${.template_name} template.
  */
@@ -314,7 +318,22 @@ public class ${mode}StructWriter extends 
AbstractFieldWriter {
     } else {
       if (writer instanceof PromotableWriter) {
         // ensure writers are initialized
+        <#if minor.class?starts_with("Decimal")>
         ((PromotableWriter)writer).getWriter(MinorType.${upperName}<#if 
minor.class?starts_with("Decimal")>, new ${minor.arrowType}(precision, scale, 
${vectName}Vector.TYPE_WIDTH * 8)</#if>);
+        <#elseif is_timestamp_tz(minor.class) || minor.class == "Duration" || 
minor.class == "FixedSizeBinary">
+          <#if minor.arrowTypeConstructorParams??>
+            <#assign constructorParams = minor.arrowTypeConstructorParams />
+          <#else>
+            <#assign constructorParams = [] />
+            <#list minor.typeParams?reverse as typeParam>
+              <#assign constructorParams = constructorParams + [ 
typeParam.name ] />
+            </#list>
+          </#if>
+        ArrowType arrowType = new 
${minor.arrowType}(${constructorParams?join(", ")});
+        ((PromotableWriter)writer).getWriter(MinorType.${upperName}, 
arrowType);
+        <#else>
+        ((PromotableWriter)writer).getWriter(MinorType.${upperName});
+        </#if>
       }
     }
     return writer;
diff --git a/java/vector/src/main/codegen/templates/UnionListWriter.java 
b/java/vector/src/main/codegen/templates/UnionListWriter.java
index 926276b5eb..fac75a9ce5 100644
--- a/java/vector/src/main/codegen/templates/UnionListWriter.java
+++ b/java/vector/src/main/codegen/templates/UnionListWriter.java
@@ -37,6 +37,10 @@ package org.apache.arrow.vector.complex.impl;
 import static org.apache.arrow.memory.util.LargeMemoryUtil.checkedCastToInt;
 <#include "/@includes/vv_imports.ftl" />
 
+<#function is_timestamp_tz type>
+  <#return type?starts_with("TimeStamp") && type?ends_with("TZ")>
+</#function>
+
 /*
  * This class is generated using freemarker and the ${.template_name} template.
  */
@@ -103,55 +107,31 @@ public class Union${listName}Writer extends 
AbstractFieldWriter {
     super.setPosition(index);
   }
 
-  <#list vv.types as type><#list type.minor as minor><#assign name = 
minor.class?cap_first />
-  <#assign fields = minor.fields!type.fields />
-  <#assign uncappedName = name?uncap_first/>
-  <#if uncappedName == "int" ><#assign uncappedName = "integer" /></#if>
-  <#if !minor.typeParams?? >
-
+  <#list vv.types as type><#list type.minor as minor>
+  <#assign lowerName = minor.class?uncap_first />
+  <#if lowerName == "int" ><#assign lowerName = "integer" /></#if>
+  <#assign upperName = minor.class?upper_case />
+  <#assign capName = minor.class?cap_first />
+  <#assign vectName = capName />
   @Override
-  public ${name}Writer ${uncappedName}() {
+  public ${minor.class}Writer ${lowerName}() {
     return this;
   }
 
+  <#if minor.typeParams?? >
   @Override
-  public ${name}Writer ${uncappedName}(String name) {
-    structName = name;
-    return writer.${uncappedName}(name);
+  public ${minor.class}Writer ${lowerName}(String name<#list minor.typeParams 
as typeParam>, ${typeParam.type} ${typeParam.name}</#list>) {
+    return writer.${lowerName}(name<#list minor.typeParams as typeParam>, 
${typeParam.name}</#list>);
   }
   </#if>
-  </#list></#list>
-
-  @Override
-  public DecimalWriter decimal() {
-    return this;
-  }
-
-  @Override
-  public DecimalWriter decimal(String name, int scale, int precision) {
-    return writer.decimal(name, scale, precision);
-  }
-
-  @Override
-  public DecimalWriter decimal(String name) {
-    return writer.decimal(name);
-  }
-
-  @Override
-  public Decimal256Writer decimal256() {
-    return this;
-  }
 
   @Override
-  public Decimal256Writer decimal256(String name, int scale, int precision) {
-    return writer.decimal256(name, scale, precision);
-  }
-
-  @Override
-  public Decimal256Writer decimal256(String name) {
-    return writer.decimal256(name);
+  public ${minor.class}Writer ${lowerName}(String name) {
+    structName = name;
+    return writer.${lowerName}(name);
   }
 
+  </#list></#list>
 
   @Override
   public StructWriter struct() {
@@ -240,18 +220,6 @@ public class Union${listName}Writer extends 
AbstractFieldWriter {
     inStruct = false;
   }
 
-  @Override
-  public void write(DecimalHolder holder) {
-    writer.write(holder);
-    writer.setPosition(writer.idx()+1);
-  }
-
-  @Override
-  public void write(Decimal256Holder holder) {
-    writer.write(holder);
-    writer.setPosition(writer.idx()+1);
-  }
-
   @Override
   public void writeNull() {
     if (!listStarted){
@@ -261,65 +229,53 @@ public class Union${listName}Writer extends 
AbstractFieldWriter {
     }
   }
 
-  public void writeDecimal(long start, ArrowBuf buffer, ArrowType arrowType) {
-    writer.writeDecimal(start, buffer, arrowType);
-    writer.setPosition(writer.idx()+1);
-  }
-
-  public void writeDecimal(long start, ArrowBuf buffer) {
-    writer.writeDecimal(start, buffer);
+  <#list vv.types as type>
+    <#list type.minor as minor>
+      <#assign name = minor.class?cap_first />
+      <#assign fields = minor.fields!type.fields />
+      <#assign uncappedName = name?uncap_first/>
+  @Override
+  public void write${name}(<#list fields as field>${field.type} 
${field.name}<#if field_has_next>, </#if></#list>) {
+    writer.write${name}(<#list fields as field>${field.name}<#if 
field_has_next>, </#if></#list>);
     writer.setPosition(writer.idx()+1);
   }
 
-  public void writeDecimal(BigDecimal value) {
-    writer.writeDecimal(value);
+  <#if is_timestamp_tz(minor.class) || minor.class == "Duration" || 
minor.class == "FixedSizeBinary">
+  @Override
+  public void write(${name}Holder holder) {
+    writer.write(holder);
     writer.setPosition(writer.idx()+1);
   }
 
-  public void writeBigEndianBytesToDecimal(byte[] value, ArrowType arrowType){
-    writer.writeBigEndianBytesToDecimal(value, arrowType);
-    writer.setPosition(writer.idx() + 1);
-  }
-
-  public void writeDecimal256(long start, ArrowBuf buffer, ArrowType 
arrowType) {
-    writer.writeDecimal256(start, buffer, arrowType);
+  <#elseif minor.class?starts_with("Decimal")>
+  public void write${name}(long start, ArrowBuf buffer, ArrowType arrowType) {
+    writer.write${name}(start, buffer, arrowType);
     writer.setPosition(writer.idx()+1);
   }
 
-  public void writeDecimal256(long start, ArrowBuf buffer) {
-    writer.writeDecimal256(start, buffer);
+  @Override
+  public void write(${name}Holder holder) {
+    writer.write(holder);
     writer.setPosition(writer.idx()+1);
   }
 
-  public void writeDecimal256(BigDecimal value) {
-    writer.writeDecimal256(value);
+  public void write${name}(BigDecimal value) {
+    writer.write${name}(value);
     writer.setPosition(writer.idx()+1);
   }
 
-  public void writeBigEndianBytesToDecimal256(byte[] value, ArrowType 
arrowType){
-    writer.writeBigEndianBytesToDecimal256(value, arrowType);
+  public void writeBigEndianBytesTo${name}(byte[] value, ArrowType arrowType){
+    writer.writeBigEndianBytesTo${name}(value, arrowType);
     writer.setPosition(writer.idx() + 1);
   }
-
-
-  <#list vv.types as type>
-    <#list type.minor as minor>
-      <#assign name = minor.class?cap_first />
-      <#assign fields = minor.fields!type.fields />
-      <#assign uncappedName = name?uncap_first/>
-      <#if !minor.typeParams?? >
+  <#else>
   @Override
-  public void write${name}(<#list fields as field>${field.type} 
${field.name}<#if field_has_next>, </#if></#list>) {
-    writer.write${name}(<#list fields as field>${field.name}<#if 
field_has_next>, </#if></#list>);
-    writer.setPosition(writer.idx()+1);
-  }
-
   public void write(${name}Holder holder) {
     writer.write${name}(<#list fields as field>holder.${field.name}<#if 
field_has_next>, </#if></#list>);
     writer.setPosition(writer.idx()+1);
   }
+  </#if>
 
-      </#if>
     </#list>
   </#list>
 }
diff --git a/java/vector/src/main/codegen/templates/UnionReader.java 
b/java/vector/src/main/codegen/templates/UnionReader.java
index 444ca9ca73..56a6cc90b3 100644
--- a/java/vector/src/main/codegen/templates/UnionReader.java
+++ b/java/vector/src/main/codegen/templates/UnionReader.java
@@ -28,6 +28,11 @@ import org.apache.arrow.vector.types.pojo.Field;
 package org.apache.arrow.vector.complex.impl;
 
 <#include "/@includes/vv_imports.ftl" />
+
+<#function is_timestamp_tz type>
+  <#return type?starts_with("TimeStamp") && type?ends_with("TZ")>
+</#function>
+
 /**
  * Source code generated using FreeMarker template ${.template_name}
  */
@@ -90,7 +95,7 @@ public class UnionReader extends AbstractFieldReader {
       <#list type.minor as minor>
         <#assign name = minor.class?cap_first />
         <#assign uncappedName = name?uncap_first/>
-        <#if !minor.typeParams?? || minor.class?starts_with("Decimal")>
+        <#if !minor.typeParams?? || minor.class?starts_with("Decimal") || 
is_timestamp_tz(minor.class) || minor.class == "Duration" || minor.class == 
"FixedSizeBinary">
     case ${name?upper_case}:
       return (FieldReader) get${name}();
         </#if>
@@ -170,7 +175,7 @@ public class UnionReader extends AbstractFieldReader {
       <#assign friendlyType = 
(minor.friendlyType!minor.boxedType!type.boxedType) />
       <#assign safeType=friendlyType />
       <#if safeType=="byte[]"><#assign safeType="ByteArray" /></#if>
-      <#if !minor.typeParams?? || minor.class?starts_with("Decimal") >
+      <#if !minor.typeParams?? || minor.class?starts_with("Decimal") || 
is_timestamp_tz(minor.class) || minor.class == "Duration" || minor.class == 
"FixedSizeBinary">
 
   private ${name}ReaderImpl ${uncappedName}Reader;
 
diff --git a/java/vector/src/main/codegen/templates/UnionVector.java 
b/java/vector/src/main/codegen/templates/UnionVector.java
index 48fa5281ea..0446faab7a 100644
--- a/java/vector/src/main/codegen/templates/UnionVector.java
+++ b/java/vector/src/main/codegen/templates/UnionVector.java
@@ -67,7 +67,9 @@ import static org.apache.arrow.vector.types.UnionMode.Sparse;
 import static org.apache.arrow.memory.util.LargeMemoryUtil.checkedCastToInt;
 import static org.apache.arrow.memory.util.LargeMemoryUtil.capAtMaxInt;
 
-
+<#function is_timestamp_tz type>
+  <#return type?starts_with("TimeStamp") && type?ends_with("TZ")>
+</#function>
 
 /*
  * This class is generated using freemarker and the ${.template_name} template.
@@ -269,18 +271,24 @@ public class UnionVector extends AbstractContainerVector 
implements FieldVector
       <#assign fields = minor.fields!type.fields />
       <#assign uncappedName = name?uncap_first/>
       <#assign lowerCaseName = name?lower_case/>
-      <#if !minor.typeParams?? || minor.class?starts_with("Decimal") >
+      <#if !minor.typeParams?? || minor.class?starts_with("Decimal") || 
is_timestamp_tz(minor.class) || minor.class == "Duration" || minor.class == 
"FixedSizeBinary">
 
   private ${name}Vector ${uncappedName}Vector;
 
-  public ${name}Vector get${name}Vector(<#if 
minor.class?starts_with("Decimal")> ArrowType arrowType</#if>) {
-    return get${name}Vector(null<#if minor.class?starts_with("Decimal")>, 
arrowType</#if>);
+  <#if minor.class?starts_with("Decimal") || is_timestamp_tz(minor.class) || 
minor.class == "Duration" || minor.class == "FixedSizeBinary">
+  public ${name}Vector get${name}Vector() {
+    if (${uncappedName}Vector == null) {
+      throw new IllegalArgumentException("No ${name} present. Provide 
ArrowType argument to create a new vector");
+    }
+    return ${uncappedName}Vector;
   }
-
-  public ${name}Vector get${name}Vector(String name<#if 
minor.class?starts_with("Decimal")>, ArrowType arrowType</#if>) {
+  public ${name}Vector get${name}Vector(ArrowType arrowType) {
+    return get${name}Vector(null, arrowType);
+  }
+  public ${name}Vector get${name}Vector(String name, ArrowType arrowType) {
     if (${uncappedName}Vector == null) {
       int vectorCount = internalStruct.size();
-      ${uncappedName}Vector = addOrGet(name, MinorType.${name?upper_case},<#if 
minor.class?starts_with("Decimal")> arrowType,</#if> ${name}Vector.class);
+      ${uncappedName}Vector = addOrGet(name, MinorType.${name?upper_case}, 
arrowType, ${name}Vector.class);
       if (internalStruct.size() > vectorCount) {
         ${uncappedName}Vector.allocateNew();
         if (callBack != null) {
@@ -290,10 +298,21 @@ public class UnionVector extends AbstractContainerVector 
implements FieldVector
     }
     return ${uncappedName}Vector;
   }
-  <#if minor.class?starts_with("Decimal")>
+  <#else>
   public ${name}Vector get${name}Vector() {
+    return get${name}Vector(null);
+  }
+
+  public ${name}Vector get${name}Vector(String name) {
     if (${uncappedName}Vector == null) {
-      throw new IllegalArgumentException("No ${uncappedName} present. Provide 
ArrowType argument to create a new vector");
+      int vectorCount = internalStruct.size();
+      ${uncappedName}Vector = addOrGet(name, MinorType.${name?upper_case}, 
${name}Vector.class);
+      if (internalStruct.size() > vectorCount) {
+        ${uncappedName}Vector.allocateNew();
+        if (callBack != null) {
+          callBack.doWork();
+        }
+      }
     }
     return ${uncappedName}Vector;
   }
@@ -658,9 +677,9 @@ public class UnionVector extends AbstractContainerVector 
implements FieldVector
           <#assign name = minor.class?cap_first />
           <#assign fields = minor.fields!type.fields />
           <#assign uncappedName = name?uncap_first/>
-          <#if !minor.typeParams?? || minor.class?starts_with("Decimal") >
+          <#if !minor.typeParams?? || minor.class?starts_with("Decimal") || 
is_timestamp_tz(minor.class) || minor.class == "Duration" || minor.class == 
"FixedSizeBinary">
         case ${name?upper_case}:
-        return get${name}Vector(name<#if minor.class?starts_with("Decimal")>, 
arrowType</#if>);
+        return get${name}Vector(name<#if minor.class?starts_with("Decimal") || 
is_timestamp_tz(minor.class) || minor.class == "Duration" || minor.class == 
"FixedSizeBinary">, arrowType</#if>);
           </#if>
         </#list>
       </#list>
@@ -745,11 +764,15 @@ public class UnionVector extends AbstractContainerVector 
implements FieldVector
           <#assign name = minor.class?cap_first />
           <#assign fields = minor.fields!type.fields />
           <#assign uncappedName = name?uncap_first/>
-          <#if !minor.typeParams?? || minor.class?starts_with("Decimal") >
+          <#if !minor.typeParams?? || minor.class?starts_with("Decimal") || 
is_timestamp_tz(minor.class) || minor.class == "Duration" || minor.class == 
"FixedSizeBinary">
       case ${name?upper_case}:
         Nullable${name}Holder ${uncappedName}Holder = new 
Nullable${name}Holder();
         reader.read(${uncappedName}Holder);
-        setSafe(index, ${uncappedName}Holder<#if 
minor.class?starts_with("Decimal")>, arrowType</#if>);
+        <#if minor.class?starts_with("Decimal") || 
is_timestamp_tz(minor.class) || minor.class == "Duration" || minor.class == 
"FixedSizeBinary">
+        setSafe(index, ${uncappedName}Holder, arrowType);
+        <#else>
+        setSafe(index, ${uncappedName}Holder);
+        </#if>
         break;
           </#if>
         </#list>
@@ -766,17 +789,24 @@ public class UnionVector extends AbstractContainerVector 
implements FieldVector
         throw new UnsupportedOperationException();
       }
     }
+
     <#list vv.types as type>
       <#list type.minor as minor>
         <#assign name = minor.class?cap_first />
         <#assign fields = minor.fields!type.fields />
         <#assign uncappedName = name?uncap_first/>
-        <#if !minor.typeParams?? || minor.class?starts_with("Decimal") >
-    public void setSafe(int index, Nullable${name}Holder holder<#if 
minor.class?starts_with("Decimal")>, ArrowType arrowType</#if>) {
+        <#if !minor.typeParams?? || minor.class?starts_with("Decimal") || 
is_timestamp_tz(minor.class) || minor.class == "Duration" || minor.class == 
"FixedSizeBinary">
+    <#if minor.class?starts_with("Decimal") || is_timestamp_tz(minor.class) || 
minor.class == "Duration" || minor.class == "FixedSizeBinary">
+    public void setSafe(int index, Nullable${name}Holder holder, ArrowType 
arrowType) {
       setType(index, MinorType.${name?upper_case});
-      get${name}Vector(null<#if minor.class?starts_with("Decimal")>, 
arrowType</#if>).setSafe(index, holder);
+      get${name}Vector(null, arrowType).setSafe(index, holder);
     }
-
+    <#else>
+    public void setSafe(int index, Nullable${name}Holder holder) {
+      setType(index, MinorType.${name?upper_case});
+      get${name}Vector(null).setSafe(index, holder);
+    }
+    </#if>
         </#if>
       </#list>
     </#list>
diff --git a/java/vector/src/main/codegen/templates/UnionWriter.java 
b/java/vector/src/main/codegen/templates/UnionWriter.java
index fc4fd7dd79..4efd1026ca 100644
--- a/java/vector/src/main/codegen/templates/UnionWriter.java
+++ b/java/vector/src/main/codegen/templates/UnionWriter.java
@@ -31,6 +31,11 @@ package org.apache.arrow.vector.complex.impl;
 import org.apache.arrow.vector.complex.writer.BaseWriter;
 import org.apache.arrow.vector.types.Types.MinorType;
 
+<#function is_timestamp_tz type>
+  <#return type?starts_with("TimeStamp") && type?ends_with("TZ")>
+</#function>
+
+
 /*
  * This class is generated using freemarker and the ${.template_name} template.
  */
@@ -183,9 +188,13 @@ public class UnionWriter extends AbstractFieldWriter 
implements FieldWriter {
         <#assign name = minor.class?cap_first />
         <#assign fields = minor.fields!type.fields />
         <#assign uncappedName = name?uncap_first/>
-        <#if !minor.typeParams?? || minor.class?starts_with("Decimal")>
+        <#if !minor.typeParams?? || minor.class?starts_with("Decimal") || 
is_timestamp_tz(minor.class) || minor.class == "Duration" || minor.class == 
"FixedSizeBinary">
     case ${name?upper_case}:
-      return get${name}Writer(<#if minor.class?starts_with("Decimal") 
>arrowType</#if>);
+      <#if minor.class?starts_with("Decimal") || is_timestamp_tz(minor.class) 
|| minor.class == "Duration" || minor.class == "FixedSizeBinary">
+      return get${name}Writer(arrowType);
+      <#else>
+      return get${name}Writer();
+      </#if>
         </#if>
       </#list>
     </#list>
@@ -199,36 +208,86 @@ public class UnionWriter extends AbstractFieldWriter 
implements FieldWriter {
       <#assign fields = minor.fields!type.fields />
       <#assign uncappedName = name?uncap_first/>
       <#assign friendlyType = 
(minor.friendlyType!minor.boxedType!type.boxedType) />
-      <#if !minor.typeParams?? || minor.class?starts_with("Decimal") >
+      <#if !minor.typeParams?? || minor.class?starts_with("Decimal") || 
is_timestamp_tz(minor.class) || minor.class == "Duration" || minor.class == 
"FixedSizeBinary">
 
   private ${name}Writer ${name?uncap_first}Writer;
 
-  private ${name}Writer get${name}Writer(<#if 
minor.class?starts_with("Decimal")>ArrowType arrowType</#if>) {
+  <#if minor.class?starts_with("Decimal") || is_timestamp_tz(minor.class) || 
minor.class == "Duration" || minor.class == "FixedSizeBinary">
+  private ${name}Writer get${name}Writer(ArrowType arrowType) {
     if (${uncappedName}Writer == null) {
-      ${uncappedName}Writer = new ${name}WriterImpl(data.get${name}Vector(<#if 
minor.class?starts_with("Decimal")>arrowType</#if>));
+      ${uncappedName}Writer = new 
${name}WriterImpl(data.get${name}Vector(arrowType));
       ${uncappedName}Writer.setPosition(idx());
       writers.add(${uncappedName}Writer);
     }
     return ${uncappedName}Writer;
   }
 
-  public ${name}Writer as${name}(<#if 
minor.class?starts_with("Decimal")>ArrowType arrowType</#if>) {
+  public ${name}Writer as${name}(ArrowType arrowType) {
     data.setType(idx(), MinorType.${name?upper_case});
-    return get${name}Writer(<#if 
minor.class?starts_with("Decimal")>arrowType</#if>);
+    return get${name}Writer(arrowType);
+  }
+  <#else>
+  private ${name}Writer get${name}Writer() {
+    if (${uncappedName}Writer == null) {
+      ${uncappedName}Writer = new ${name}WriterImpl(data.get${name}Vector());
+      ${uncappedName}Writer.setPosition(idx());
+      writers.add(${uncappedName}Writer);
+    }
+    return ${uncappedName}Writer;
   }
 
+  public ${name}Writer as${name}() {
+    data.setType(idx(), MinorType.${name?upper_case});
+    return get${name}Writer();
+  }
+  </#if>
+
   @Override
   public void write(${name}Holder holder) {
     data.setType(idx(), MinorType.${name?upper_case});
-    <#if minor.class?starts_with("Decimal")>ArrowType arrowType = new 
ArrowType.Decimal(holder.precision, holder.scale, ${name}Holder.WIDTH * 
8);</#if>
-    get${name}Writer(<#if 
minor.class?starts_with("Decimal")>arrowType</#if>).setPosition(idx());
-    get${name}Writer(<#if 
minor.class?starts_with("Decimal")>arrowType</#if>).write${name}(<#list fields 
as field>holder.${field.name}<#if field_has_next>, </#if></#list><#if 
minor.class?starts_with("Decimal")>, arrowType</#if>);
+    <#if minor.class?starts_with("Decimal")>
+    ArrowType arrowType = new ArrowType.Decimal(holder.precision, 
holder.scale, ${name}Holder.WIDTH * 8);
+    get${name}Writer(arrowType).setPosition(idx());
+    get${name}Writer(arrowType).write${name}(<#list fields as 
field>holder.${field.name}<#if field_has_next>, </#if></#list>, arrowType);
+    <#elseif is_timestamp_tz(minor.class)>
+    ArrowType.Timestamp arrowTypeWithoutTz = (ArrowType.Timestamp) 
MinorType.${name?upper_case?remove_ending("TZ")}.getType();
+    ArrowType arrowType = new 
ArrowType.Timestamp(arrowTypeWithoutTz.getUnit(), holder.timezone);
+    get${name}Writer(arrowType).setPosition(idx());
+    get${name}Writer(arrowType).write(holder);
+    <#elseif minor.class == "Duration">
+    ArrowType arrowType = new ArrowType.Duration(holder.unit);
+    get${name}Writer(arrowType).setPosition(idx());
+    get${name}Writer(arrowType).write(holder);
+    <#elseif minor.class == "FixedSizeBinary">
+    ArrowType arrowType = new ArrowType.FixedSizeBinary(holder.byteWidth);
+    get${name}Writer(arrowType).setPosition(idx());
+    get${name}Writer(arrowType).write(holder);
+    <#else>
+    get${name}Writer().setPosition(idx());
+    get${name}Writer().write${name}(<#list fields as 
field>holder.${field.name}<#if field_has_next>, </#if></#list>);
+    </#if>
   }
 
   public void write${minor.class}(<#list fields as field>${field.type} 
${field.name}<#if field_has_next>, </#if></#list><#if 
minor.class?starts_with("Decimal")>, ArrowType arrowType</#if>) {
     data.setType(idx(), MinorType.${name?upper_case});
-    get${name}Writer(<#if 
minor.class?starts_with("Decimal")>arrowType</#if>).setPosition(idx());
-    get${name}Writer(<#if 
minor.class?starts_with("Decimal")>arrowType</#if>).write${name}(<#list fields 
as field>${field.name}<#if field_has_next>, </#if></#list><#if 
minor.class?starts_with("Decimal")>, arrowType</#if>);
+    <#if minor.class?starts_with("Decimal")>
+    get${name}Writer(arrowType).setPosition(idx());
+    get${name}Writer(arrowType).write${name}(<#list fields as 
field>${field.name}<#if field_has_next>, </#if></#list>, arrowType);
+    <#elseif is_timestamp_tz(minor.class)>
+    ArrowType.Timestamp arrowTypeWithoutTz = (ArrowType.Timestamp) 
MinorType.${name?upper_case?remove_ending("TZ")}.getType();
+    ArrowType arrowType = new 
ArrowType.Timestamp(arrowTypeWithoutTz.getUnit(), "UTC");
+    get${name}Writer(arrowType).setPosition(idx());
+    get${name}Writer(arrowType).write${name}(<#list fields as 
field>${field.name}<#if field_has_next>, </#if></#list>);
+    <#elseif minor.class == "Duration" || minor.class == "FixedSizeBinary">
+    // This is expected to throw. There's nothing more that we can do here 
since we can't infer any
+    // sort of default unit for the Duration or a default width for the 
FixedSizeBinary types.
+    ArrowType arrowType = MinorType.${name?upper_case}.getType();
+    get${name}Writer(arrowType).setPosition(idx());
+    get${name}Writer(arrowType).write${name}(<#list fields as 
field>${field.name}<#if field_has_next>, </#if></#list>);
+    <#else>
+    get${name}Writer().setPosition(idx());
+    get${name}Writer().write${name}(<#list fields as field>${field.name}<#if 
field_has_next>, </#if></#list>);
+    </#if>
   }
   <#if minor.class?starts_with("Decimal")>
   public void write${name}(${friendlyType} value) {
@@ -312,7 +371,7 @@ public class UnionWriter extends AbstractFieldWriter 
implements FieldWriter {
   <#if lowerName == "int" ><#assign lowerName = "integer" /></#if>
   <#assign upperName = minor.class?upper_case />
   <#assign capName = minor.class?cap_first />
-  <#if !minor.typeParams?? || minor.class?starts_with("Decimal") >
+  <#if !minor.typeParams?? || minor.class?starts_with("Decimal") || 
is_timestamp_tz(minor.class) || minor.class == "Duration" || minor.class == 
"FixedSizeBinary">
   @Override
   public ${capName}Writer ${lowerName}(String name) {
     data.setType(idx(), MinorType.STRUCT);
@@ -327,7 +386,7 @@ public class UnionWriter extends AbstractFieldWriter 
implements FieldWriter {
     return getListWriter().${lowerName}();
   }
   </#if>
-  <#if minor.class?starts_with("Decimal")>
+  <#if minor.class?starts_with("Decimal") || is_timestamp_tz(minor.class) || 
minor.class == "Duration" || minor.class == "FixedSizeBinary">
   @Override
   public ${capName}Writer ${lowerName}(String name<#list minor.typeParams as 
typeParam>, ${typeParam.type} ${typeParam.name}</#list>) {
     data.setType(idx(), MinorType.STRUCT);
diff --git 
a/java/vector/src/main/java/org/apache/arrow/vector/DurationVector.java 
b/java/vector/src/main/java/org/apache/arrow/vector/DurationVector.java
index 9671b34e00..7af21a8ecd 100644
--- a/java/vector/src/main/java/org/apache/arrow/vector/DurationVector.java
+++ b/java/vector/src/main/java/org/apache/arrow/vector/DurationVector.java
@@ -141,6 +141,7 @@ public final class DurationVector extends 
BaseFixedWidthVector {
     }
     holder.isSet = 1;
     holder.value = get(valueBuffer, index);
+    holder.unit = this.unit;
   }
 
   /**
@@ -241,6 +242,9 @@ public final class DurationVector extends 
BaseFixedWidthVector {
   public void set(int index, NullableDurationHolder holder) throws 
IllegalArgumentException {
     if (holder.isSet < 0) {
       throw new IllegalArgumentException();
+    } else if (!this.unit.equals(holder.unit)) {
+      throw new IllegalArgumentException(
+          String.format("holder.unit: %s not equal to vector unit: %s", 
holder.unit, this.unit));
     } else if (holder.isSet > 0) {
       set(index, holder.value);
     } else {
@@ -255,6 +259,10 @@ public final class DurationVector extends 
BaseFixedWidthVector {
    * @param holder  data holder for value of element
    */
   public void set(int index, DurationHolder holder) {
+    if (!this.unit.equals(holder.unit)) {
+      throw new IllegalArgumentException(
+          String.format("holder.unit: %s not equal to vector unit: %s", 
holder.unit, this.unit));
+    }
     set(index, holder.value);
   }
 
diff --git 
a/java/vector/src/main/java/org/apache/arrow/vector/FixedSizeBinaryVector.java 
b/java/vector/src/main/java/org/apache/arrow/vector/FixedSizeBinaryVector.java
index e1847e4bb9..f9ea37e4c3 100644
--- 
a/java/vector/src/main/java/org/apache/arrow/vector/FixedSizeBinaryVector.java
+++ 
b/java/vector/src/main/java/org/apache/arrow/vector/FixedSizeBinaryVector.java
@@ -138,6 +138,7 @@ public class FixedSizeBinaryVector extends 
BaseFixedWidthVector {
     }
     holder.isSet = 1;
     holder.buffer = valueBuffer.slice((long) index * byteWidth, byteWidth);
+    holder.byteWidth = byteWidth;
   }
 
   /**
@@ -257,7 +258,10 @@ public class FixedSizeBinaryVector extends 
BaseFixedWidthVector {
    * @param holder  holder that carries data buffer.
    */
   public void set(int index, FixedSizeBinaryHolder holder) {
-    assert holder.byteWidth == byteWidth;
+    if (this.byteWidth != holder.byteWidth) {
+      throw new IllegalArgumentException(
+          String.format("holder.byteWidth: %d not equal to vector byteWidth: 
%d", holder.byteWidth, this.byteWidth));
+    }
     set(index, holder.buffer);
   }
 
@@ -282,9 +286,11 @@ public class FixedSizeBinaryVector extends 
BaseFixedWidthVector {
    * @param holder  holder that carries data buffer.
    */
   public void set(int index, NullableFixedSizeBinaryHolder holder) {
-    assert holder.byteWidth == byteWidth;
     if (holder.isSet < 0) {
       throw new IllegalArgumentException("holder has a negative isSet value");
+    } else if (this.byteWidth != holder.byteWidth) {
+      throw new IllegalArgumentException(
+          String.format("holder.byteWidth: %d not equal to vector byteWidth: 
%d", holder.byteWidth, this.byteWidth));
     } else if (holder.isSet > 0) {
       set(index, holder.buffer);
     } else {
diff --git 
a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMicroTZVector.java 
b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMicroTZVector.java
index d08b352306..e083392ffe 100644
--- 
a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMicroTZVector.java
+++ 
b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMicroTZVector.java
@@ -132,6 +132,7 @@ public final class TimeStampMicroTZVector extends 
TimeStampVector {
     }
     holder.isSet = 1;
     holder.value = valueBuffer.getLong((long) index * TYPE_WIDTH);
+    holder.timezone = timeZone;
   }
 
   /**
@@ -167,6 +168,9 @@ public final class TimeStampMicroTZVector extends 
TimeStampVector {
   public void set(int index, NullableTimeStampMicroTZHolder holder) throws 
IllegalArgumentException {
     if (holder.isSet < 0) {
       throw new IllegalArgumentException();
+    } else if (!this.timeZone.equals(holder.timezone)) {
+      throw new IllegalArgumentException(
+          String.format("holder.timezone: %s not equal to vector timezone: 
%s", holder.timezone, this.timeZone));
     } else if (holder.isSet > 0) {
       BitVectorHelper.setBit(validityBuffer, index);
       setValue(index, holder.value);
@@ -182,6 +186,10 @@ public final class TimeStampMicroTZVector extends 
TimeStampVector {
    * @param holder  data holder for value of element
    */
   public void set(int index, TimeStampMicroTZHolder holder) {
+    if (!this.timeZone.equals(holder.timezone)) {
+      throw new IllegalArgumentException(
+          String.format("holder.timezone: %s not equal to vector timezone: 
%s", holder.timezone, this.timeZone));
+    }
     BitVectorHelper.setBit(validityBuffer, index);
     setValue(index, holder.value);
   }
diff --git 
a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMilliTZVector.java 
b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMilliTZVector.java
index 1151d064e2..d01a43aa1b 100644
--- 
a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMilliTZVector.java
+++ 
b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampMilliTZVector.java
@@ -132,6 +132,7 @@ public final class TimeStampMilliTZVector extends 
TimeStampVector {
     }
     holder.isSet = 1;
     holder.value = valueBuffer.getLong((long) index * TYPE_WIDTH);
+    holder.timezone = timeZone;
   }
 
   /**
@@ -167,6 +168,9 @@ public final class TimeStampMilliTZVector extends 
TimeStampVector {
   public void set(int index, NullableTimeStampMilliTZHolder holder) throws 
IllegalArgumentException {
     if (holder.isSet < 0) {
       throw new IllegalArgumentException();
+    } else if (!this.timeZone.equals(holder.timezone)) {
+      throw new IllegalArgumentException(
+          String.format("holder.timezone: %s not equal to vector timezone: 
%s", holder.timezone, this.timeZone));
     } else if (holder.isSet > 0) {
       BitVectorHelper.setBit(validityBuffer, index);
       setValue(index, holder.value);
@@ -182,6 +186,10 @@ public final class TimeStampMilliTZVector extends 
TimeStampVector {
    * @param holder  data holder for value of element
    */
   public void set(int index, TimeStampMilliTZHolder holder) {
+    if (!this.timeZone.equals(holder.timezone)) {
+      throw new IllegalArgumentException(
+          String.format("holder.timezone: %s not equal to vector timezone: 
%s", holder.timezone, this.timeZone));
+    }
     BitVectorHelper.setBit(validityBuffer, index);
     setValue(index, holder.value);
   }
diff --git 
a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampNanoTZVector.java 
b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampNanoTZVector.java
index b19b437781..2a51babda1 100644
--- 
a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampNanoTZVector.java
+++ 
b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampNanoTZVector.java
@@ -132,6 +132,7 @@ public final class TimeStampNanoTZVector extends 
TimeStampVector {
     }
     holder.isSet = 1;
     holder.value = valueBuffer.getLong((long) index * TYPE_WIDTH);
+    holder.timezone = timeZone;
   }
 
   /**
@@ -167,6 +168,9 @@ public final class TimeStampNanoTZVector extends 
TimeStampVector {
   public void set(int index, NullableTimeStampNanoTZHolder holder) throws 
IllegalArgumentException {
     if (holder.isSet < 0) {
       throw new IllegalArgumentException();
+    } else if (!this.timeZone.equals(holder.timezone)) {
+      throw new IllegalArgumentException(
+          String.format("holder.timezone: %s not equal to vector timezone: 
%s", holder.timezone, this.timeZone));
     } else if (holder.isSet > 0) {
       BitVectorHelper.setBit(validityBuffer, index);
       setValue(index, holder.value);
@@ -182,6 +186,10 @@ public final class TimeStampNanoTZVector extends 
TimeStampVector {
    * @param holder  data holder for value of element
    */
   public void set(int index, TimeStampNanoTZHolder holder) {
+    if (!this.timeZone.equals(holder.timezone)) {
+      throw new IllegalArgumentException(
+          String.format("holder.timezone: %s not equal to vector timezone: 
%s", holder.timezone, this.timeZone));
+    }
     BitVectorHelper.setBit(validityBuffer, index);
     setValue(index, holder.value);
   }
diff --git 
a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampSecTZVector.java 
b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampSecTZVector.java
index 1ffdb55c7a..47e796e995 100644
--- 
a/java/vector/src/main/java/org/apache/arrow/vector/TimeStampSecTZVector.java
+++ 
b/java/vector/src/main/java/org/apache/arrow/vector/TimeStampSecTZVector.java
@@ -132,6 +132,7 @@ public final class TimeStampSecTZVector extends 
TimeStampVector {
     }
     holder.isSet = 1;
     holder.value = valueBuffer.getLong((long) index * TYPE_WIDTH);
+    holder.timezone = timeZone;
   }
 
   /**
@@ -167,6 +168,9 @@ public final class TimeStampSecTZVector extends 
TimeStampVector {
   public void set(int index, NullableTimeStampSecTZHolder holder) throws 
IllegalArgumentException {
     if (holder.isSet < 0) {
       throw new IllegalArgumentException();
+    } else if (!this.timeZone.equals(holder.timezone)) {
+      throw new IllegalArgumentException(
+          String.format("holder.timezone: %s not equal to vector timezone: 
%s", holder.timezone, this.timeZone));
     } else if (holder.isSet > 0) {
       BitVectorHelper.setBit(validityBuffer, index);
       setValue(index, holder.value);
@@ -182,6 +186,10 @@ public final class TimeStampSecTZVector extends 
TimeStampVector {
    * @param holder  data holder for value of element
    */
   public void set(int index, TimeStampSecTZHolder holder) {
+    if (!this.timeZone.equals(holder.timezone)) {
+      throw new IllegalArgumentException(
+          String.format("holder.timezone: %s not equal to vector timezone: 
%s", holder.timezone, this.timeZone));
+    }
     BitVectorHelper.setBit(validityBuffer, index);
     setValue(index, holder.value);
   }
diff --git 
a/java/vector/src/main/java/org/apache/arrow/vector/complex/impl/PromotableWriter.java
 
b/java/vector/src/main/java/org/apache/arrow/vector/complex/impl/PromotableWriter.java
index 06b064fdaa..d99efceae3 100644
--- 
a/java/vector/src/main/java/org/apache/arrow/vector/complex/impl/PromotableWriter.java
+++ 
b/java/vector/src/main/java/org/apache/arrow/vector/complex/impl/PromotableWriter.java
@@ -247,10 +247,18 @@ public class PromotableWriter extends 
AbstractPromotableFieldWriter {
     }
   }
 
+  private boolean requiresArrowType(MinorType type) {
+    return type == MinorType.DECIMAL ||
+        type == MinorType.MAP ||
+        type == MinorType.DURATION ||
+        type == MinorType.FIXEDSIZEBINARY ||
+        (type.name().startsWith("TIMESTAMP") && type.name().endsWith("TZ"));
+  }
+
   @Override
   protected FieldWriter getWriter(MinorType type, ArrowType arrowType) {
     if (state == State.UNION) {
-      if (type == MinorType.DECIMAL || type == MinorType.MAP) {
+      if (requiresArrowType(type)) {
         ((UnionWriter) writer).getWriter(type, arrowType);
       } else {
         ((UnionWriter) writer).getWriter(type);
@@ -277,7 +285,7 @@ public class PromotableWriter extends 
AbstractPromotableFieldWriter {
       writer.setPosition(position);
     } else if (type != this.type) {
       promoteToUnion();
-      if (type == MinorType.DECIMAL || type == MinorType.MAP) {
+      if (requiresArrowType(type)) {
         ((UnionWriter) writer).getWriter(type, arrowType);
       } else {
         ((UnionWriter) writer).getWriter(type);
diff --git 
a/java/vector/src/test/java/org/apache/arrow/vector/TestFixedSizeBinaryVector.java
 
b/java/vector/src/test/java/org/apache/arrow/vector/TestFixedSizeBinaryVector.java
index 363821e983..e8f764a21e 100644
--- 
a/java/vector/src/test/java/org/apache/arrow/vector/TestFixedSizeBinaryVector.java
+++ 
b/java/vector/src/test/java/org/apache/arrow/vector/TestFixedSizeBinaryVector.java
@@ -207,25 +207,25 @@ public class TestFixedSizeBinaryVector {
     try {
       vector.set(0, smallValue);
       failWithException(errorMsg);
-    } catch (AssertionError ignore) {
+    } catch (AssertionError | IllegalArgumentException ignore) {
     }
 
     try {
       vector.set(0, smallHolder);
       failWithException(errorMsg);
-    } catch (AssertionError ignore) {
+    } catch (AssertionError | IllegalArgumentException ignore) {
     }
 
     try {
       vector.set(0, smallNullableHolder);
       failWithException(errorMsg);
-    } catch (AssertionError ignore) {
+    } catch (AssertionError | IllegalArgumentException ignore) {
     }
 
     try {
       vector.set(0, smallBuf);
       failWithException(errorMsg);
-    } catch (AssertionError ignore) {
+    } catch (AssertionError | IllegalArgumentException ignore) {
     }
 
     // test large inputs, byteWidth matches but value or buffer is bigger than 
byteWidth
@@ -243,25 +243,25 @@ public class TestFixedSizeBinaryVector {
     try {
       vector.setSafe(0, smallValue);
       failWithException(errorMsg);
-    } catch (AssertionError ignore) {
+    } catch (AssertionError | IllegalArgumentException ignore) {
     }
 
     try {
       vector.setSafe(0, smallHolder);
       failWithException(errorMsg);
-    } catch (AssertionError ignore) {
+    } catch (AssertionError | IllegalArgumentException ignore) {
     }
 
     try {
       vector.setSafe(0, smallNullableHolder);
       failWithException(errorMsg);
-    } catch (AssertionError ignore) {
+    } catch (AssertionError | IllegalArgumentException ignore) {
     }
 
     try {
       vector.setSafe(0, smallBuf);
       failWithException(errorMsg);
-    } catch (AssertionError ignore) {
+    } catch (AssertionError | IllegalArgumentException ignore) {
     }
 
     // test large inputs, byteWidth matches but value or buffer is bigger than 
byteWidth
diff --git 
a/java/vector/src/test/java/org/apache/arrow/vector/TestListVector.java 
b/java/vector/src/test/java/org/apache/arrow/vector/TestListVector.java
index ffeedf04d0..f0f19058ee 100644
--- a/java/vector/src/test/java/org/apache/arrow/vector/TestListVector.java
+++ b/java/vector/src/test/java/org/apache/arrow/vector/TestListVector.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -28,10 +29,15 @@ import java.util.List;
 
 import org.apache.arrow.memory.ArrowBuf;
 import org.apache.arrow.memory.BufferAllocator;
+import org.apache.arrow.util.AutoCloseables;
 import org.apache.arrow.vector.complex.BaseRepeatedValueVector;
 import org.apache.arrow.vector.complex.ListVector;
 import org.apache.arrow.vector.complex.impl.UnionListWriter;
 import org.apache.arrow.vector.complex.reader.FieldReader;
+import org.apache.arrow.vector.holders.DurationHolder;
+import org.apache.arrow.vector.holders.FixedSizeBinaryHolder;
+import org.apache.arrow.vector.holders.TimeStampMilliTZHolder;
+import org.apache.arrow.vector.types.TimeUnit;
 import org.apache.arrow.vector.types.Types.MinorType;
 import org.apache.arrow.vector.types.pojo.ArrowType;
 import org.apache.arrow.vector.types.pojo.Field;
@@ -898,6 +904,144 @@ public class TestListVector {
     }
   }
 
+  @Test
+  public void testWriterGetTimestampMilliTZField() {
+    try (final ListVector vector = ListVector.empty("list", allocator)) {
+      org.apache.arrow.vector.complex.writer.FieldWriter writer = 
vector.getWriter();
+      writer.allocate();
+
+      writer.startList();
+      writer.timeStampMilliTZ().writeTimeStampMilliTZ(1000L);
+      writer.timeStampMilliTZ().writeTimeStampMilliTZ(2000L);
+      writer.endList();
+      vector.setValueCount(1);
+
+      Field expectedDataField = new 
Field(BaseRepeatedValueVector.DATA_VECTOR_NAME,
+          FieldType.nullable(new ArrowType.Timestamp(TimeUnit.MILLISECOND, 
"UTC")), null);
+      Field expectedField = new Field(vector.getName(), 
FieldType.nullable(ArrowType.List.INSTANCE),
+          Arrays.asList(expectedDataField));
+
+      assertEquals(expectedField, writer.getField());
+    }
+  }
+
+  @Test
+  public void testWriterUsingHolderGetTimestampMilliTZField() {
+    try (final ListVector vector = ListVector.empty("list", allocator)) {
+      org.apache.arrow.vector.complex.writer.FieldWriter writer = 
vector.getWriter();
+      writer.allocate();
+
+      TimeStampMilliTZHolder holder = new TimeStampMilliTZHolder();
+      holder.timezone = "SomeFakeTimeZone";
+      writer.startList();
+      holder.value = 12341234L;
+      writer.timeStampMilliTZ().write(holder);
+      holder.value = 55555L;
+      writer.timeStampMilliTZ().write(holder);
+
+      // Writing with a different timezone should throw
+      holder.timezone = "AsdfTimeZone";
+      holder.value = 77777;
+      IllegalArgumentException ex = 
assertThrows(IllegalArgumentException.class,
+          () -> writer.timeStampMilliTZ().write(holder));
+      assertEquals("holder.timezone: AsdfTimeZone not equal to vector 
timezone: SomeFakeTimeZone", ex.getMessage());
+
+      writer.endList();
+      vector.setValueCount(1);
+
+      Field expectedDataField = new 
Field(BaseRepeatedValueVector.DATA_VECTOR_NAME,
+          FieldType.nullable(new ArrowType.Timestamp(TimeUnit.MILLISECOND, 
"SomeFakeTimeZone")), null);
+      Field expectedField = new Field(vector.getName(), 
FieldType.nullable(ArrowType.List.INSTANCE),
+          Arrays.asList(expectedDataField));
+
+      assertEquals(expectedField, writer.getField());
+    }
+  }
+
+  @Test
+  public void testWriterGetDurationField() {
+    try (final ListVector vector = ListVector.empty("list", allocator)) {
+      org.apache.arrow.vector.complex.writer.FieldWriter writer = 
vector.getWriter();
+      writer.allocate();
+
+      DurationHolder durationHolder = new DurationHolder();
+      durationHolder.unit = TimeUnit.MILLISECOND;
+
+      writer.startList();
+      durationHolder.value = 812374L;
+      writer.duration().write(durationHolder);
+      durationHolder.value = 143451L;
+      writer.duration().write(durationHolder);
+
+      // Writing with a different unit should throw
+      durationHolder.unit = TimeUnit.SECOND;
+      durationHolder.value = 8888888;
+      IllegalArgumentException ex = 
assertThrows(IllegalArgumentException.class,
+          () -> writer.duration().write(durationHolder));
+      assertEquals("holder.unit: SECOND not equal to vector unit: 
MILLISECOND", ex.getMessage());
+
+      writer.endList();
+      vector.setValueCount(1);
+
+      Field expectedDataField = new 
Field(BaseRepeatedValueVector.DATA_VECTOR_NAME,
+          FieldType.nullable(new ArrowType.Duration(TimeUnit.MILLISECOND)), 
null);
+      Field expectedField = new Field(vector.getName(), 
FieldType.nullable(ArrowType.List.INSTANCE),
+          Arrays.asList(expectedDataField));
+
+      assertEquals(expectedField, writer.getField());
+    }
+  }
+
+  @Test
+  public void testWriterGetFixedSizeBinaryField() throws Exception {
+    // Adapted from: TestComplexWriter.java:fixedSizeBinaryWriters
+    // test values
+    int numValues = 10;
+    int byteWidth = 9;
+    byte[][] values = new byte[numValues][byteWidth];
+    for (int i = 0; i < numValues; i++) {
+      for (int j = 0; j < byteWidth; j++) {
+        values[i][j] = ((byte) i);
+      }
+    }
+    ArrowBuf[] bufs = new ArrowBuf[numValues];
+    for (int i = 0; i < numValues; i++) {
+      bufs[i] = allocator.buffer(byteWidth);
+      bufs[i].setBytes(0, values[i]);
+    }
+
+    try (final ListVector vector = ListVector.empty("list", allocator)) {
+      org.apache.arrow.vector.complex.writer.FieldWriter writer = 
vector.getWriter();
+      writer.allocate();
+
+      FixedSizeBinaryHolder binHolder = new FixedSizeBinaryHolder();
+      binHolder.byteWidth = byteWidth;
+      writer.startList();
+      for (int i = 0; i < numValues; i++) {
+        binHolder.buffer = bufs[i];
+        writer.fixedSizeBinary().write(binHolder);
+      }
+
+      // Writing with a different byteWidth should throw
+      // Note just reusing the last buffer value since that won't matter here 
anyway
+      binHolder.byteWidth = 3;
+      IllegalArgumentException ex = 
assertThrows(IllegalArgumentException.class,
+          () -> writer.fixedSizeBinary().write(binHolder));
+      assertEquals("holder.byteWidth: 3 not equal to vector byteWidth: 9", 
ex.getMessage());
+
+      writer.endList();
+      vector.setValueCount(1);
+
+      Field expectedDataField = new 
Field(BaseRepeatedValueVector.DATA_VECTOR_NAME,
+          FieldType.nullable(new ArrowType.FixedSizeBinary(byteWidth)), null);
+      Field expectedField = new Field(vector.getName(), 
FieldType.nullable(ArrowType.List.INSTANCE),
+          Arrays.asList(expectedDataField));
+
+      assertEquals(expectedField, writer.getField());
+    }
+    AutoCloseables.close(bufs);
+  }
+
   @Test
   public void testClose() throws Exception {
     try (final ListVector vector = ListVector.empty("list", allocator)) {
diff --git 
a/java/vector/src/test/java/org/apache/arrow/vector/TestUnionVector.java 
b/java/vector/src/test/java/org/apache/arrow/vector/TestUnionVector.java
index f04998915b..b53171a597 100644
--- a/java/vector/src/test/java/org/apache/arrow/vector/TestUnionVector.java
+++ b/java/vector/src/test/java/org/apache/arrow/vector/TestUnionVector.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -497,6 +498,28 @@ public class TestUnionVector {
     }
   }
 
+  @Test
+  public void testCreateNewVectorWithoutTypeExceptionThrown() {
+    try (UnionVector vector =
+        new UnionVector(EMPTY_SCHEMA_PATH, allocator, /* field type */ null, 
/* call-back */ null)) {
+      IllegalArgumentException e1 = 
assertThrows(IllegalArgumentException.class,
+          () -> vector.getTimeStampMilliTZVector());
+      assertEquals("No TimeStampMilliTZ present. Provide ArrowType argument to 
create a new vector", e1.getMessage());
+
+      IllegalArgumentException e2 = 
assertThrows(IllegalArgumentException.class,
+          () -> vector.getDurationVector());
+      assertEquals("No Duration present. Provide ArrowType argument to create 
a new vector", e2.getMessage());
+
+      IllegalArgumentException e3 = 
assertThrows(IllegalArgumentException.class,
+          () -> vector.getFixedSizeBinaryVector());
+      assertEquals("No FixedSizeBinary present. Provide ArrowType argument to 
create a new vector", e3.getMessage());
+
+      IllegalArgumentException e4 = 
assertThrows(IllegalArgumentException.class,
+          () -> vector.getDecimalVector());
+      assertEquals("No Decimal present. Provide ArrowType argument to create a 
new vector", e4.getMessage());
+    }
+  }
+
   private static NullableIntHolder newIntHolder(int value) {
     final NullableIntHolder holder = new NullableIntHolder();
     holder.isSet = 1;
diff --git 
a/java/vector/src/test/java/org/apache/arrow/vector/complex/impl/TestPromotableWriter.java
 
b/java/vector/src/test/java/org/apache/arrow/vector/complex/impl/TestPromotableWriter.java
index 9dce33122e..1068f7c030 100644
--- 
a/java/vector/src/test/java/org/apache/arrow/vector/complex/impl/TestPromotableWriter.java
+++ 
b/java/vector/src/test/java/org/apache/arrow/vector/complex/impl/TestPromotableWriter.java
@@ -20,7 +20,12 @@ package org.apache.arrow.vector.complex.impl;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import org.apache.arrow.memory.ArrowBuf;
 import org.apache.arrow.memory.BufferAllocator;
 import org.apache.arrow.vector.DirtyRootAllocator;
 import org.apache.arrow.vector.complex.ListVector;
@@ -28,7 +33,13 @@ import 
org.apache.arrow.vector.complex.NonNullableStructVector;
 import org.apache.arrow.vector.complex.StructVector;
 import org.apache.arrow.vector.complex.UnionVector;
 import org.apache.arrow.vector.complex.writer.BaseWriter.StructWriter;
+import org.apache.arrow.vector.holders.DurationHolder;
+import org.apache.arrow.vector.holders.FixedSizeBinaryHolder;
+import org.apache.arrow.vector.holders.NullableTimeStampMilliTZHolder;
+import org.apache.arrow.vector.holders.TimeStampMilliTZHolder;
+import org.apache.arrow.vector.types.TimeUnit;
 import org.apache.arrow.vector.types.Types;
+import org.apache.arrow.vector.types.pojo.ArrowType;
 import org.apache.arrow.vector.types.pojo.ArrowType.ArrowTypeID;
 import org.apache.arrow.vector.types.pojo.Field;
 import org.apache.arrow.vector.types.pojo.FieldType;
@@ -78,9 +89,35 @@ public class TestPromotableWriter {
       writer.setPosition(4);
       writer.integer("A").writeInt(100);
 
+      writer.setPosition(5);
+      writer.timeStampMilliTZ("A").writeTimeStampMilliTZ(123123);
+
+      // Also try the holder version for timeStampMilliTZ
+      writer.setPosition(6);
+      TimeStampMilliTZHolder tsmtzHolder = new TimeStampMilliTZHolder();
+      // This has to be UTC since the vector above was initialized using the 
non holder
+      // version that defaults to UTC.
+      tsmtzHolder.timezone = "UTC";
+      tsmtzHolder.value = 12345L;
+      writer.timeStampMilliTZ("A").write(tsmtzHolder);
+
+      writer.setPosition(7);
+      DurationHolder durationHolder = new DurationHolder();
+      durationHolder.unit = TimeUnit.SECOND;
+      durationHolder.value = 444413;
+      writer.duration("A").write(durationHolder);
+
+      writer.setPosition(8);
+      ArrowBuf buf = allocator.buffer(4);
+      buf.setInt(0, 18978);
+      FixedSizeBinaryHolder binHolder = new FixedSizeBinaryHolder();
+      binHolder.byteWidth = 4;
+      binHolder.buffer = buf;
+      writer.fixedSizeBinary("A", 4).write(binHolder);
+
       writer.end();
 
-      container.setValueCount(5);
+      container.setValueCount(9);
 
       final UnionVector uv = v.getChild("A", UnionVector.class);
 
@@ -98,6 +135,22 @@ public class TestPromotableWriter {
       assertFalse("4 shouldn't be null", uv.isNull(4));
       assertEquals(100, uv.getObject(4));
 
+      assertFalse("5 shouldn't be null", uv.isNull(5));
+      assertEquals(123123L, uv.getObject(5));
+
+      assertFalse("6 shouldn't be null", uv.isNull(6));
+      NullableTimeStampMilliTZHolder readBackHolder = new 
NullableTimeStampMilliTZHolder();
+      uv.getTimeStampMilliTZVector().get(6, readBackHolder);
+      assertEquals(12345L, readBackHolder.value);
+      assertEquals("UTC", readBackHolder.timezone);
+
+      assertFalse("7 shouldn't be null", uv.isNull(7));
+      assertEquals(444413L, ((java.time.Duration) 
uv.getObject(7)).getSeconds());
+
+      assertFalse("8 shouldn't be null", uv.isNull(8));
+      assertEquals(18978,
+          
ByteBuffer.wrap(uv.getFixedSizeBinaryVector().get(8)).order(ByteOrder.nativeOrder()).getInt());
+
       container.clear();
       container.allocateNew();
 
@@ -116,11 +169,13 @@ public class TestPromotableWriter {
           childField1.getName(), ArrowTypeID.Union, 
childField1.getType().getTypeID());
       assertEquals("Child field should be decimal type: " +
           childField2.getName(), ArrowTypeID.Decimal, 
childField2.getType().getTypeID());
+
+      buf.close();
     }
   }
 
   @Test
-  public void testNoPromoteToUnionWithNull() throws Exception {
+  public void testNoPromoteFloat4ToUnionWithNull() throws Exception {
 
     try (final NonNullableStructVector container = 
NonNullableStructVector.empty(EMPTY_SCHEMA_PATH, allocator);
          final StructVector v = container.addOrGetStruct("test");
@@ -136,7 +191,6 @@ public class TestPromotableWriter {
       FieldType childTypeOfListInContainer = 
container.getField().getChildren().get(0).getChildren().get(0)
               .getChildren().get(0).getFieldType();
 
-
       // create a listvector with same type as list in container to, say, hold 
a copy
       // this will be a nullvector
       ListVector lv = ListVector.empty("name", allocator);
@@ -164,4 +218,178 @@ public class TestPromotableWriter {
       lv.close();
     }
   }
+
+  @Test
+  public void testNoPromoteTimeStampMilliTZToUnionWithNull() throws Exception {
+
+    try (final NonNullableStructVector container = 
NonNullableStructVector.empty(EMPTY_SCHEMA_PATH, allocator);
+         final StructVector v = container.addOrGetStruct("test");
+         final PromotableWriter writer = new PromotableWriter(v, container)) {
+
+      container.allocateNew();
+
+      writer.start();
+      writer.list("list").startList();
+      writer.list("list").endList();
+      writer.end();
+
+      FieldType childTypeOfListInContainer = 
container.getField().getChildren().get(0).getChildren().get(0)
+              .getChildren().get(0).getFieldType();
+
+      // create a listvector with same type as list in container to, say, hold 
a copy
+      // this will be a nullvector
+      ListVector lv = ListVector.empty("name", allocator);
+      lv.addOrGetVector(childTypeOfListInContainer);
+      assertEquals(childTypeOfListInContainer.getType(), 
Types.MinorType.NULL.getType());
+      assertEquals(lv.getChildrenFromFields().get(0).getMinorType().getType(), 
Types.MinorType.NULL.getType());
+
+      writer.start();
+      writer.list("list").startList();
+      TimeStampMilliTZHolder holder = new TimeStampMilliTZHolder();
+      holder.value = 12341234L;
+      holder.timezone = "FakeTimeZone";
+      writer.list("list").timeStampMilliTZ().write(holder);
+
+      // Test that we get an exception when the timezone doesn't match
+      holder.timezone = "SomeTimeZone";
+      IllegalArgumentException ex = 
assertThrows(IllegalArgumentException.class,
+          () -> writer.list("list").timeStampMilliTZ().write(holder));
+      assertEquals("holder.timezone: SomeTimeZone not equal to vector 
timezone: FakeTimeZone", ex.getMessage());
+
+      writer.list("list").endList();
+      writer.end();
+
+      container.setValueCount(2);
+
+      childTypeOfListInContainer = 
container.getField().getChildren().get(0).getChildren().get(0)
+              .getChildren().get(0).getFieldType();
+
+      // repeat but now the type in container has been changed from null to 
float
+      // we expect same behaviour from listvector
+      lv.addOrGetVector(childTypeOfListInContainer);
+      assertEquals(childTypeOfListInContainer.getType(),
+          new ArrowType.Timestamp(TimeUnit.MILLISECOND, "FakeTimeZone"));
+      assertEquals(lv.getChildrenFromFields().get(0).getField().getType(),
+          new ArrowType.Timestamp(TimeUnit.MILLISECOND, "FakeTimeZone"));
+
+      lv.close();
+    }
+  }
+
+  @Test
+  public void testNoPromoteDurationToUnionWithNull() throws Exception {
+
+    try (final NonNullableStructVector container = 
NonNullableStructVector.empty(EMPTY_SCHEMA_PATH, allocator);
+         final StructVector v = container.addOrGetStruct("test");
+         final PromotableWriter writer = new PromotableWriter(v, container)) {
+
+      container.allocateNew();
+
+      writer.start();
+      writer.list("list").startList();
+      writer.list("list").endList();
+      writer.end();
+
+      FieldType childTypeOfListInContainer = 
container.getField().getChildren().get(0).getChildren().get(0)
+              .getChildren().get(0).getFieldType();
+
+      // create a listvector with same type as list in container to, say, hold 
a copy
+      // this will be a nullvector
+      ListVector lv = ListVector.empty("name", allocator);
+      lv.addOrGetVector(childTypeOfListInContainer);
+      assertEquals(childTypeOfListInContainer.getType(), 
Types.MinorType.NULL.getType());
+      assertEquals(lv.getChildrenFromFields().get(0).getMinorType().getType(), 
Types.MinorType.NULL.getType());
+
+      writer.start();
+      writer.list("list").startList();
+      DurationHolder holder = new DurationHolder();
+      holder.unit = TimeUnit.NANOSECOND;
+      holder.value = 567657L;
+      writer.list("list").duration().write(holder);
+
+      // Test that we get an exception when the unit doesn't match
+      holder.unit = TimeUnit.MICROSECOND;
+      IllegalArgumentException ex = 
assertThrows(IllegalArgumentException.class,
+          () -> writer.list("list").duration().write(holder));
+      assertEquals("holder.unit: MICROSECOND not equal to vector unit: 
NANOSECOND", ex.getMessage());
+
+      writer.list("list").endList();
+      writer.end();
+
+      container.setValueCount(2);
+
+      childTypeOfListInContainer = 
container.getField().getChildren().get(0).getChildren().get(0)
+              .getChildren().get(0).getFieldType();
+
+      // repeat but now the type in container has been changed from null to 
float
+      // we expect same behaviour from listvector
+      lv.addOrGetVector(childTypeOfListInContainer);
+      assertEquals(childTypeOfListInContainer.getType(),
+          new ArrowType.Duration(TimeUnit.NANOSECOND));
+      assertEquals(lv.getChildrenFromFields().get(0).getField().getType(),
+          new ArrowType.Duration(TimeUnit.NANOSECOND));
+
+      lv.close();
+    }
+  }
+
+  @Test
+  public void testNoPromoteFixedSizeBinaryToUnionWithNull() throws Exception {
+
+    try (final NonNullableStructVector container = 
NonNullableStructVector.empty(EMPTY_SCHEMA_PATH, allocator);
+         final StructVector v = container.addOrGetStruct("test");
+         final PromotableWriter writer = new PromotableWriter(v, container)) {
+
+      container.allocateNew();
+
+      writer.start();
+      writer.list("list").startList();
+      writer.list("list").endList();
+      writer.end();
+
+      FieldType childTypeOfListInContainer = 
container.getField().getChildren().get(0).getChildren().get(0)
+              .getChildren().get(0).getFieldType();
+
+      // create a listvector with same type as list in container to, say, hold 
a copy
+      // this will be a nullvector
+      ListVector lv = ListVector.empty("name", allocator);
+      lv.addOrGetVector(childTypeOfListInContainer);
+      assertEquals(childTypeOfListInContainer.getType(), 
Types.MinorType.NULL.getType());
+      assertEquals(lv.getChildrenFromFields().get(0).getMinorType().getType(), 
Types.MinorType.NULL.getType());
+
+      writer.start();
+      writer.list("list").startList();
+      ArrowBuf buf = allocator.buffer(4);
+      buf.setInt(0, 22222);
+      FixedSizeBinaryHolder holder = new FixedSizeBinaryHolder();
+      holder.byteWidth = 4;
+      holder.buffer = buf;
+      writer.list("list").fixedSizeBinary().write(holder);
+
+      // Test that we get an exception when the unit doesn't match
+      holder.byteWidth = 7;
+      IllegalArgumentException ex = 
assertThrows(IllegalArgumentException.class,
+          () -> writer.list("list").fixedSizeBinary().write(holder));
+      assertEquals("holder.byteWidth: 7 not equal to vector byteWidth: 4", 
ex.getMessage());
+
+      writer.list("list").endList();
+      writer.end();
+
+      container.setValueCount(2);
+
+      childTypeOfListInContainer = 
container.getField().getChildren().get(0).getChildren().get(0)
+              .getChildren().get(0).getFieldType();
+
+      // repeat but now the type in container has been changed from null to 
float
+      // we expect same behaviour from listvector
+      lv.addOrGetVector(childTypeOfListInContainer);
+      assertEquals(childTypeOfListInContainer.getType(),
+          new ArrowType.FixedSizeBinary(4));
+      assertEquals(lv.getChildrenFromFields().get(0).getField().getType(),
+          new ArrowType.FixedSizeBinary(4));
+
+      lv.close();
+      buf.close();
+    }
+  }
 }
diff --git 
a/java/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java
 
b/java/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java
index 5504149665..9f7f66083a 100644
--- 
a/java/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java
+++ 
b/java/vector/src/test/java/org/apache/arrow/vector/complex/writer/TestComplexWriter.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.*;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -61,8 +62,15 @@ import 
org.apache.arrow.vector.complex.writer.BaseWriter.ListWriter;
 import org.apache.arrow.vector.complex.writer.BaseWriter.MapWriter;
 import org.apache.arrow.vector.complex.writer.BaseWriter.StructWriter;
 import org.apache.arrow.vector.holders.DecimalHolder;
+import org.apache.arrow.vector.holders.DurationHolder;
+import org.apache.arrow.vector.holders.FixedSizeBinaryHolder;
 import org.apache.arrow.vector.holders.IntHolder;
+import org.apache.arrow.vector.holders.NullableDurationHolder;
+import org.apache.arrow.vector.holders.NullableFixedSizeBinaryHolder;
+import org.apache.arrow.vector.holders.NullableTimeStampMilliTZHolder;
 import org.apache.arrow.vector.holders.NullableTimeStampNanoTZHolder;
+import org.apache.arrow.vector.holders.TimeStampMilliTZHolder;
+import org.apache.arrow.vector.types.TimeUnit;
 import org.apache.arrow.vector.types.pojo.ArrowType;
 import org.apache.arrow.vector.types.pojo.ArrowType.ArrowTypeID;
 import org.apache.arrow.vector.types.pojo.ArrowType.Int;
@@ -354,6 +362,125 @@ public class TestComplexWriter {
     }
   }
 
+  @Test
+  public void listTimeStampMilliTZType() {
+    try (ListVector listVector = ListVector.empty("list", allocator)) {
+      listVector.allocateNew();
+      UnionListWriter listWriter = new UnionListWriter(listVector);
+      for (int i = 0; i < COUNT; i++) {
+        listWriter.startList();
+        for (int j = 0; j < i % 7; j++) {
+          if (j % 2 == 0) {
+            listWriter.writeNull();
+          } else {
+            TimeStampMilliTZHolder holder = new TimeStampMilliTZHolder();
+            holder.timezone = "FakeTimeZone";
+            holder.value = j;
+            listWriter.timeStampMilliTZ().write(holder);
+          }
+        }
+        listWriter.endList();
+      }
+      listWriter.setValueCount(COUNT);
+      UnionListReader listReader = new UnionListReader(listVector);
+      for (int i = 0; i < COUNT; i++) {
+        listReader.setPosition(i);
+        for (int j = 0; j < i % 7; j++) {
+          listReader.next();
+          if (j % 2 == 0) {
+            assertFalse("index is set: " + j, listReader.reader().isSet());
+          } else {
+            NullableTimeStampMilliTZHolder actual = new 
NullableTimeStampMilliTZHolder();
+            listReader.reader().read(actual);
+            assertEquals(j, actual.value);
+            assertEquals("FakeTimeZone", actual.timezone);
+          }
+        }
+      }
+    }
+  }
+
+  @Test
+  public void listDurationType() {
+    try (ListVector listVector = ListVector.empty("list", allocator)) {
+      listVector.allocateNew();
+      UnionListWriter listWriter = new UnionListWriter(listVector);
+      for (int i = 0; i < COUNT; i++) {
+        listWriter.startList();
+        for (int j = 0; j < i % 7; j++) {
+          if (j % 2 == 0) {
+            listWriter.writeNull();
+          } else {
+            DurationHolder holder = new DurationHolder();
+            holder.unit = TimeUnit.MICROSECOND;
+            holder.value = j;
+            listWriter.duration().write(holder);
+          }
+        }
+        listWriter.endList();
+      }
+      listWriter.setValueCount(COUNT);
+      UnionListReader listReader = new UnionListReader(listVector);
+      for (int i = 0; i < COUNT; i++) {
+        listReader.setPosition(i);
+        for (int j = 0; j < i % 7; j++) {
+          listReader.next();
+          if (j % 2 == 0) {
+            assertFalse("index is set: " + j, listReader.reader().isSet());
+          } else {
+            NullableDurationHolder actual = new NullableDurationHolder();
+            listReader.reader().read(actual);
+            assertEquals(TimeUnit.MICROSECOND, actual.unit);
+            assertEquals(j, actual.value);
+          }
+        }
+      }
+    }
+  }
+
+  @Test
+  public void listFixedSizeBinaryType() throws Exception {
+    List<ArrowBuf> bufs = new ArrayList<ArrowBuf>();
+    try (ListVector listVector = ListVector.empty("list", allocator)) {
+      listVector.allocateNew();
+      UnionListWriter listWriter = new UnionListWriter(listVector);
+      for (int i = 0; i < COUNT; i++) {
+        listWriter.startList();
+        for (int j = 0; j < i % 7; j++) {
+          if (j % 2 == 0) {
+            listWriter.writeNull();
+          } else {
+            ArrowBuf buf = allocator.buffer(4);
+            buf.setInt(0, j);
+            FixedSizeBinaryHolder holder = new FixedSizeBinaryHolder();
+            holder.byteWidth = 4;
+            holder.buffer = buf;
+            listWriter.fixedSizeBinary().write(holder);
+            bufs.add(buf);
+          }
+        }
+        listWriter.endList();
+      }
+      listWriter.setValueCount(COUNT);
+      UnionListReader listReader = new UnionListReader(listVector);
+      for (int i = 0; i < COUNT; i++) {
+        listReader.setPosition(i);
+        for (int j = 0; j < i % 7; j++) {
+          listReader.next();
+          if (j % 2 == 0) {
+            assertFalse("index is set: " + j, listReader.reader().isSet());
+          } else {
+            NullableFixedSizeBinaryHolder actual = new 
NullableFixedSizeBinaryHolder();
+            listReader.reader().read(actual);
+            assertEquals(j, actual.buffer.getInt(0));
+            assertEquals(4, actual.byteWidth);
+          }
+        }
+      }
+    }
+    AutoCloseables.close(bufs);
+  }
+
   @Test
   public void listScalarTypeNullable() {
     try (ListVector listVector = ListVector.empty("list", allocator)) {
@@ -605,14 +732,33 @@ public class TestComplexWriter {
   }
 
   @Test
-  public void simpleUnion() {
+  public void simpleUnion() throws Exception {
+    List<ArrowBuf> bufs = new ArrayList<ArrowBuf>();
     UnionVector vector = new UnionVector("union", allocator, /* field type */ 
null, /* call-back */ null);
     UnionWriter unionWriter = new UnionWriter(vector);
     unionWriter.allocate();
     for (int i = 0; i < COUNT; i++) {
       unionWriter.setPosition(i);
-      if (i % 2 == 0) {
+      if (i % 5 == 0) {
         unionWriter.writeInt(i);
+      } else if (i % 5 == 1) {
+        TimeStampMilliTZHolder holder = new TimeStampMilliTZHolder();
+        holder.value = (long) i;
+        holder.timezone = "AsdfTimeZone";
+        unionWriter.write(holder);
+      } else if (i % 5 == 2) {
+        DurationHolder holder = new DurationHolder();
+        holder.value = (long) i;
+        holder.unit = TimeUnit.NANOSECOND;
+        unionWriter.write(holder);
+      } else if (i % 5 == 3) {
+        FixedSizeBinaryHolder holder = new FixedSizeBinaryHolder();
+        ArrowBuf buf = allocator.buffer(4);
+        buf.setInt(0, i);
+        holder.byteWidth = 4;
+        holder.buffer = buf;
+        unionWriter.write(holder);
+        bufs.add(buf);
       } else {
         unionWriter.writeFloat4((float) i);
       }
@@ -621,13 +767,29 @@ public class TestComplexWriter {
     UnionReader unionReader = new UnionReader(vector);
     for (int i = 0; i < COUNT; i++) {
       unionReader.setPosition(i);
-      if (i % 2 == 0) {
+      if (i % 5 == 0) {
         Assert.assertEquals(i, i, unionReader.readInteger());
+      } else if (i % 5 == 1) {
+        NullableTimeStampMilliTZHolder holder = new 
NullableTimeStampMilliTZHolder();
+        unionReader.read(holder);
+        Assert.assertEquals(i, holder.value);
+        Assert.assertEquals("AsdfTimeZone", holder.timezone);
+      } else if (i % 5 == 2) {
+        NullableDurationHolder holder = new NullableDurationHolder();
+        unionReader.read(holder);
+        Assert.assertEquals(i, holder.value);
+        Assert.assertEquals(TimeUnit.NANOSECOND, holder.unit);
+      } else if (i % 5 == 3) {
+        NullableFixedSizeBinaryHolder holder = new 
NullableFixedSizeBinaryHolder();
+        unionReader.read(holder);
+        assertEquals(i, holder.buffer.getInt(0));
+        assertEquals(4, holder.byteWidth);
       } else {
         Assert.assertEquals((float) i, unionReader.readFloat(), 1e-12);
       }
     }
     vector.close();
+    AutoCloseables.close(bufs);
   }
 
   @Test

Reply via email to