Overview

7 User-defined types

This chapter deepens the practical use of Apache Thrift by focusing on user-defined types (UDTs) as the core of cross-language data exchange. Building on transports, protocols, and IDL syntax, it shows how to declare rich, portable data models that serialize cleanly across C++, Java, Python, and more, using generated read/write methods. UDTs are equally valuable in RPC and non-RPC workflows—moving safely through message queues, files, or databases—demonstrated with a simple EarthRelPosition example and a more realistic RadioObservation model, all while leveraging Thrift’s pluggable I/O stack.

Effective type design uses Thrift’s core tools—structs, unions, enums, typedefs, base types, and collections—combined with disciplined practices. Field IDs must be positive, unique, and never reused; deleted fields are retired (commented out) to preserve compatibility. Requiredness matters: default is a safe general choice, optional is best for large or situational data (like an image), and required should be used sparingly because it cannot evolve. Unions communicate intentional type variability (for positions captured in different coordinate systems), maps vs. lists capture semantics like uniqueness, typedefs convey meaning (Time), and enums should keep stable numeric values while handling unknowns gracefully. Generated code patterns vary by language—e.g., setter/isset methods in Java, __isset flags in C++, and None checks in Python—but the interoperability contract is consistent.

Under the hood, generated classes include field metadata plus read/write methods that serialize by field ID and type, not name; readers loop, test IDs and types, and skip what they don’t recognize, enabling forward and backward compatibility. The chapter outlines robust evolution strategies: rename fields freely without changing semantics; add fields as default (ideally with defaults) or optional; delete by retiring IDs; avoid changing a field’s type unless you planned for it with a union; and treat changes to requiredness and default values with care. Finally, it shows whole-object compression for storage efficiency using a layered TZlibTransport beneath any protocol, achieving strong size reductions (especially with binary payloads) while preserving cross-language compatibility and tunable compression levels.

Apache Thrift Framework user-defined types.
The Arecibo Radio Telescope is an example RadioObservationSystem that might contribute RadioObservation data for use with the radio_observation.cpp program.
A Graphviz diagram of the radio_observation type interface.
The 25KB quasar.bmp used in the sky_bmp field of the RadioObservation test object created by DiskSer.java.
Programs using different versions of an interface can communicate with proper use of interface evolution in Apache Thrift.
Type serialization Zlib compression stack.

      Summary

Apache Thrift IDL provides a rich set of tools for describing data types that can be exchanged across languages and platforms. The Apache Thrift framework also provides a flexible and comprehensive set of type serialization features.

  • Structs are the Apache Thrift IDL mechanism for creating cross-language user-defined types (UDTs).
  • Structs have one or more fields each with a name, Id, type, requiredness, and optionally, a default value.
  • Optional requiredness fields offer the most serialization flexibility allowing user code to decide whether to serialize them or not.
    • o Optional fields are a good choice for any data that may not need to be serialized on all occasions, particularly data fields of large size.
    • o Optional fields must typically be set with language specific set methods to ensure that the UDT serializes them when the write() method is called.
    • o Optional fields must be tested for existence after deserialization and prior to access in case they were not found during the deserialization process.
  • Typedefs allow new semantic types to be created from existing types.
  • Enums allow new enumeration types to be created.
  • Unions are used to create fields that have more than one possible type or representation.
    • o All union fields are optional.
    • o Union values must be set with set methods in most languages.
    • o Only one type should be set at a time within a union.
  • UDTs can be serialized by calling their write() method and de-serialized by calling their read() method.
  • Apache Thrift Interface Evolution features allow UDTs to change over time without breaking existing applications.
    • o New fields can be added.
    • o Old fields can be removed.
    • o Fields can be represented with a selection of types when unions are used.
  • The TZlibTransport can be layered on top of memory and file end point transports to compress serialized objects.
    • o The TZlibTransport isn’t supported by all languages.

FAQ

What are Apache Thrift user-defined types (UDTs) and how do I create and use them?UDTs are custom data structures you define in Thrift IDL and use across languages. Typical workflow: - Describe the type in IDL (struct/union/enum, etc.). - Run the Thrift compiler (thrift --gen ) to generate native code. - Use the generated type in your application like any other class/struct. - Serialize/deserialize with the generated read()/write() methods over a chosen protocol and transport (for example, TBinaryProtocol over TMemoryBuffer or a file/network transport).
Why are numeric field IDs important, and what rules should I follow?Thrift protocols identify fields by their numeric ID and type, not their name. - Use positive, unique 16-bit IDs within each struct/union. - Never reuse IDs; retire them if a field is removed (leave a commented placeholder in IDL). - Do not change a field’s type for an existing ID; older/newer programs will skip mismatched types. - Field order in the stream is not significant; IDs drive decoding.
How do required, optional, and default requiredness differ, and which should I use?- default (no keyword): Common choice. Serialized when set; not required to be present on read. - optional: May be omitted; receivers must test for presence before using. Good for large or infrequently needed data. - required: Must always be present; missing values cause runtime errors and block evolution. Avoid unless truly mandatory forever. Tip: Prefer default; use optional for large/occasional fields; avoid required for evolvability.
When should I use structs, enums, typedefs, unions, and collections?- struct: Group related fields into a logical record (for example, RadioObservation). - enum: Small, stable sets best expressed by names (for example, RadioObservationSystem). - typedef: Give semantic meaning to a base type (for example, typedef double Time). - union: A field that can take one of several types (for example, different position representations). - collections: list/set/map for repeating groups; choose the one whose semantics match your needs (for example, map for frequency→amplitude).
How are optional fields and unions handled in Java, Python, and C++?- Java: Generated setters/unsetters/testers (for example, setSky_bmp(), unsetSky_bmp(), isSetSky_bmp()). Use union setters (for example, pos.setIcrfpos(...)) to set the active member. - Python: Optional fields are None when unset; test for None. Unions expose selected members as attributes; unset members are None. - C++: Each type has an __isset struct of bool flags (for example, ro.__isset.sky_bmp). Unions track which member is active; check the corresponding __isset or helper methods.
How do the generated write() and read() methods work?- write(): Begins a struct, then for each set field writes its ID and type, followed by the value. Collections are iterated; nested types call their own write(). Ends with a STOP marker. - read(): Loops fields until STOP; for each, uses ID and type to decide whether to read or skip. Unknown fields are skipped, order is irrelevant, optional/default fields may be absent, and unions/nested types invoke their own read().
How can I evolve a UDT without breaking existing systems?- Renaming fields: Safe (names aren’t serialized). - Adding fields: Safe if new fields are default (prefer with IDL default values) or optional. Old readers ignore unknown fields; new readers must tolerate missing ones. - Deleting fields: Comment out and retire the ID; never reuse it. Don’t delete required fields. - Changing field type: Breaks cross-version exchange for that field. Prefer modeling flexibility with a union from the start. - Changing requiredness: Avoid changing required. default→optional is usually safe; optional→required is unsafe post-release. - Changing default values: Can be subtle; different default initializations across versions may surprise receivers.
What are best practices for Thrift collections and map keys?- Choose the collection that matches intent: list (ordered), set (unique), map (key→value, unique keys). - Prefer integers, strings, or enums as keys. - Avoid floating-point keys (precision/rounding issues) and complex types as keys (poor cross-language support). - Use map when you need uniqueness and fast lookup (for example, frequency→amplitude).
How do I serialize UDTs to disk or other endpoints, and can I switch protocols/transports?Yes. The protocol and transport layers are pluggable. - Protocols: TBinaryProtocol (fast/balanced), TCompactProtocol (smaller), TJSONProtocol (human-readable). - Transports: TMemoryBuffer (in-memory), TSimpleFileTransport (files), sockets, etc. - Always open(), write()/flush() as needed, then close(). Use the same protocol for read and write. Generated code is cross-language compatible when using compatible protocols/transports.
How do I compress serialized data with Zlib in Thrift?Use the layered TZlibTransport between your protocol and endpoint transport. - Write path: endpoint → TZlibTransport → protocol → object.write(...). Call flush() on TZlibTransport to finish compression. - Read path: endpoint → TZlibTransport → protocol → object.read(...). - Works across languages (C++, Java, Python) and supports compression levels (for example, 0–9). Compression level affects only output; any level can be decompressed by a proper Zlib transport.

pro $24.99 per month

  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose one free eBook per month to keep
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime

lite $19.99 per month

  • access to all Manning books, including MEAPs!

team

5, 10 or 20 seats+ for your team - learn more


choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Programmer’s Guide to Apache Thrift ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Programmer’s Guide to Apache Thrift ebook for free
choose your plan

team

monthly
annual
$49.99
$499.99
only $41.67 per month
  • five seats for your team
  • access to all Manning books, MEAPs, liveVideos, liveProjects, and audiobooks!
  • choose another free product every time you renew
  • choose twelve free products per year
  • exclusive 50% discount on all purchases
  • renews monthly, pause or cancel renewal anytime
  • renews annually, pause or cancel renewal anytime
  • Programmer’s Guide to Apache Thrift ebook for free