Skip to content

Add support to configure adding jackson @JsonPOJOBuilder annotations to generated builders#232

Open
maff wants to merge 2 commits intoRandgalt:masterfrom
maff:feature-jackson-annotations
Open

Add support to configure adding jackson @JsonPOJOBuilder annotations to generated builders#232
maff wants to merge 2 commits intoRandgalt:masterfrom
maff:feature-jackson-annotations

Conversation

@maff
Copy link
Copy Markdown
Contributor

@maff maff commented Jun 3, 2025

Adds a new option structure to instruct record-builder to add a Jackson @JsonPOJOBuilder annotation to the generated builder. While this implements library-specifics, it does not add Jackson as dependency (only on the test module).

Example usage:

Auto-detect Jackson version (default)

@RecordBuilder.Options(jackson = @RecordBuilder.JacksonConfig(jsonPOJOBuilder = true))

Explicitly add Jackson 2.x annotation (fail if J2 is not available):

@RecordBuilder.Options(
    jackson = @RecordBuilder.JacksonConfig(
        jsonPOJOBuilder = true,
        version = JacksonVersion.JACKSON_2
    )
)

With custom setter prefix:

@RecordBuilder.Options(
    jackson = @RecordBuilder.JacksonConfig(jsonPOJOBuilder = true),
    setterPrefix = "set"
)

Closes #229

@maff
Copy link
Copy Markdown
Contributor Author

maff commented Jun 13, 2025

@Randgalt any feedback on this approach? I would be great to have this as an option for projects relying on Jackson and still wanting to benefit from RecordBuilder's initialization & defaults logic.

return;
}

final var annotationSpec = AnnotationSpec
Copy link
Copy Markdown

@semyon-levin-workato semyon-levin-workato Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid it's not that simple. Recently, Jackson 3 was released with changed package names. Is it possible to check what annotations are presented on the classpath and add appropriate annotation here?

@maff maff force-pushed the feature-jackson-annotations branch from 83b9c46 to 227ce69 Compare February 19, 2026 20:43
Introduces a dedicated Jackson option structure. JSONPojoBuilder support
can be enabled with a boolean flag.

Detects available jackson version on the classpath and adds the
respective annotations when enabled. Jackson version can be set to AUTO
(adds annotation for every found version) or to JACKSON_2 or JACKSON_3
(only adds annotations for defined version, fails the build when library
is not on the classpath).
@maff maff force-pushed the feature-jackson-annotations branch from 227ce69 to 408c108 Compare February 19, 2026 20:49
@maff
Copy link
Copy Markdown
Contributor Author

maff commented Feb 19, 2026

@Randgalt @semyon-levin-workato Updated to support both Jackson 2 and 3 by detecting available classes on the classpath, with support to specify a specific version in case both versions are present.

Would be awesome if we could progress with this feature for better out-of-the-box Jackson compatibility.

@semyon-levin-workato
Copy link
Copy Markdown

@Randgalt could you please look into this?

Comment on lines +54 to +91
case AUTO -> {
if (!jackson2Present && !jackson3Present) {
processingEnv.getMessager().printMessage(ERROR,
"jackson.jsonPOJOBuilder is enabled but Jackson is not found on classpath. "
+ "Add jackson-databind dependency or disable jsonPOJOBuilder.");
return;
}

if (jackson2Present) {
addJacksonAnnotations(metaData, builder, JACKSON_2_ANNOTATION_PACKAGE);
}

if (jackson3Present) {
addJacksonAnnotations(metaData, builder, JACKSON_3_ANNOTATION_PACKAGE);
}
}

case JACKSON_2 -> {
if (!jackson2Present) {
processingEnv.getMessager().printMessage(ERROR,
"jackson.version is set to JACKSON_2 but Jackson 2.x is not found on classpath. "
+ "Add jackson-databind 2.x dependency or change version to AUTO.");
return;
}

addJacksonAnnotations(metaData, builder, JACKSON_2_ANNOTATION_PACKAGE);
}

case JACKSON_3 -> {
if (!jackson3Present) {
processingEnv.getMessager().printMessage(ERROR,
"jackson.version is set to JACKSON_3 but Jackson 3.x is not found on classpath. "
+ "Add jackson-databind 3.x dependency or change version to AUTO.");
return;
}

addJacksonAnnotations(metaData, builder, JACKSON_3_ANNOTATION_PACKAGE);
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove those returns

Suggested change
case AUTO -> {
if (!jackson2Present && !jackson3Present) {
processingEnv.getMessager().printMessage(ERROR,
"jackson.jsonPOJOBuilder is enabled but Jackson is not found on classpath. "
+ "Add jackson-databind dependency or disable jsonPOJOBuilder.");
return;
}
if (jackson2Present) {
addJacksonAnnotations(metaData, builder, JACKSON_2_ANNOTATION_PACKAGE);
}
if (jackson3Present) {
addJacksonAnnotations(metaData, builder, JACKSON_3_ANNOTATION_PACKAGE);
}
}
case JACKSON_2 -> {
if (!jackson2Present) {
processingEnv.getMessager().printMessage(ERROR,
"jackson.version is set to JACKSON_2 but Jackson 2.x is not found on classpath. "
+ "Add jackson-databind 2.x dependency or change version to AUTO.");
return;
}
addJacksonAnnotations(metaData, builder, JACKSON_2_ANNOTATION_PACKAGE);
}
case JACKSON_3 -> {
if (!jackson3Present) {
processingEnv.getMessager().printMessage(ERROR,
"jackson.version is set to JACKSON_3 but Jackson 3.x is not found on classpath. "
+ "Add jackson-databind 3.x dependency or change version to AUTO.");
return;
}
addJacksonAnnotations(metaData, builder, JACKSON_3_ANNOTATION_PACKAGE);
}
case AUTO -> {
if (!jackson2Present && !jackson3Present) {
processingEnv.getMessager().printMessage(ERROR,
"jackson.jsonPOJOBuilder is enabled but Jackson is not found on classpath. "
+ "Add jackson-databind dependency or disable jsonPOJOBuilder.");
}
else if (jackson2Present) {
addJacksonAnnotations(metaData, builder, JACKSON_2_ANNOTATION_PACKAGE);
}
else /*if (jackson3Present)*/ {
addJacksonAnnotations(metaData, builder, JACKSON_3_ANNOTATION_PACKAGE);
}
}
case JACKSON_2 -> {
if (!jackson2Present) {
processingEnv.getMessager().printMessage(ERROR,
"jackson.version is set to JACKSON_2 but Jackson 2.x is not found on classpath. "
+ "Add jackson-databind 2.x dependency or change version to AUTO.");
}
else {
addJacksonAnnotations(metaData, builder, JACKSON_2_ANNOTATION_PACKAGE);
}
}
case JACKSON_3 -> {
if (!jackson3Present) {
processingEnv.getMessager().printMessage(ERROR,
"jackson.version is set to JACKSON_3 but Jackson 3.x is not found on classpath. "
+ "Add jackson-databind 3.x dependency or change version to AUTO.");
} else {
addJacksonAnnotations(metaData, builder, JACKSON_3_ANNOTATION_PACKAGE);
}
}

Comment thread pom.xml
<version>${jackson-version}</version>
<version>${jackson2-version}</version>
</dependency>
<dependency>
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: add a new line here

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please squash the two commits

* Specifies which Jackson version(s) to use when generating builder annotations.
*/
enum JacksonVersion {
/**
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a NONE attribute here and remove @JacksonConfig. I'd also rename this RecordBuilderJacksonVersion or something to avoid name collisions with other projects (I realize some other names in here suffer from this).

*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.ANNOTATION_TYPE)
@interface JacksonConfig {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment below. We can remove this annotation.

*
* @see JacksonConfig
*/
JacksonConfig jackson() default @JacksonConfig;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
JacksonConfig jackson() default @JacksonConfig;
JacksonVersion jackson() default JacksonVersion.NONE;

jackson3Present = isAnnotationClassPresent(JACKSON_3_ANNOTATION_PACKAGE, JSON_POJO_BUILDER);
}

private boolean isAnnotationClassPresent(String packageName, String className) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

className is always JSON_POJO_BUILDER

private final boolean jackson2Present;
private final boolean jackson3Present;

JacksonSupport(ProcessingEnvironment processingEnv) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given how this is used it would be better as a utility class with a private ctor and the methods becoming static.

return processingEnv.getElementUtils().getTypeElement(packageName + "." + className) != null;
}

public void addJacksonAnnotations(RecordBuilder.Options metaData, TypeSpec.Builder builder) {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be package-private

Comment on lines +49 to +51
if (!anyJacksonAnnotationEnabled(metaData)) {
return;
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unnecessary right?


private void addJsonPOJOBuilderAnnotation(RecordBuilder.Options metaData, TypeSpec.Builder builder,
String packageName) {
final var annotationSpec = AnnotationSpec.builder(ClassName.get(packageName, JSON_POJO_BUILDER))
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also need to consider metaData.builderMethodName() and metaData.withClassMethodPrefix() right? JsonPOJOBuilder has attributes for that.

@Randgalt
Copy link
Copy Markdown
Owner

Randgalt commented Mar 26, 2026

My apologies for how long this has taken but I just haven't had a lot of time recently. I've done a first pass review. In general I'm very positive on this. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make RecordBuilder Jackson-friendly

3 participants