How to customize Avro generated code

One common way to use Avro is to generate Java code from the Avro schema. This works well until we start using the generated code and we face difficulties in having a proper design on top of it.

Luckily, there is a way to customize the generated code by the Maven plugin. We'll need 2 projects for this: one with customizations and the other with actual code generation.

Customization project

Let's try to add static constructors for single field types:

public class AvroGeneratorExtensions {

    public String staticConstructor(SpecificCompiler specificCompiler, Schema schema) {
        if (schema.getFields().size() != 1) {
            return "";
        }
        String parameters = createParameters(specificCompiler, schema);
        String setters = createSetters(schema);
        return "public static " + specificCompiler.javaType(schema) + " of(" + parameters + ") {return newBuilder()" + setters + ".build();}";
    }

    private String createSetters(Schema schema) {
        String setters = schema.getFields().stream()
                .map(f -> "." + generateSetMethod(schema, f) + "(" + f.name() + ")")
                .collect(joining("\n"));
        return setters;
    }

    private String createParameters(SpecificCompiler specificCompiler, Schema schema) {
        String parameters = schema.getFields().stream()
                .map(f -> specificCompiler.javaType(f.schema()) + " " + f.name())
                .collect(joining(", "));
        return parameters;
    }

This class must be compiled and published as a jar file.

Messages project

In the main project, we need to configure the build like:

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.avro</groupId>
                <artifactId>avro-maven-plugin</artifactId>
                <version>${avro.version}</version>
                <dependencies>
                    <dependency>
                        <!-- add above project here -->
                    </dependency>
                </dependencies>
                <executions>
                    <execution>
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>schema</goal>
                            <goal>protocol</goal>
                            <goal>idl-protocol</goal>
                        </goals>
                        <configuration>
                            <sourceDirectory>${project.basedir}/src/main/avro/</sourceDirectory>
                            <templateDirectory>${project.basedir}/src/main/resources/templates/</templateDirectory>
                            <stringType>String</stringType>
                            <fieldVisibility>private</fieldVisibility>
                            <velocityToolsClassesNames>AvroGeneratorExtensions</velocityToolsClassesNames>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

Velocity templates must be extracted from avro jar file into src/main/resources/templates/. These are:

  • enum.vm
  • fixed.vm
  • protocol.vm
  • record.vm

Add $avrogeneratorextensions.staticConstructor($this, $schema) to record.vm to generate the static constructors.

A working example can be found here:

Yeah, this looks like a huge hack but I am not aware of a better way to customize generated code. Templates must be updated each time a new version of Avro library is used and the development process is basically trial and error.