https://github.com/spring-projects/spring-framework/blob/67b1420b32cadef349bb60490b6e07cc5f882ecc/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java#L570-L574

Currently the Jackson2ObjectMapperBuilderCustomizer is only able to replace the modules that should be installed; however, it is possible to define multiple Spring Boot Jackson2ObjectMapperBuilderCustomizer beans that could each wish to add a Jackson module. However the later customizers will always remove the modules configured by the previous instance. It isn't possible to get the current list of modules either and extend that.

We wish to separate our customizers by their related modules/source libraries (api-docs, error-handling, ..., and the application itself).

So it would be nice if there was a addModules(toInstall) method that would add the modules to the list instead of replacing it.

Most of the other methods such as serializers(JsonSerializer<?>...) already work like that:

https://github.com/spring-projects/spring-framework/blob/67b1420b32cadef349bb60490b6e07cc5f882ecc/spring-web/src/main/java/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.java#L354-L363

Thanks for your awesome work.

Comment From: aooohan

@sbrannen hi,Sam. Does this issue need to be achieved? I personally think it would be more flexible to provide a method to add a Module separately, if it need to be implemented, can I be assigned? I would be happy to provide PR.

Comment From: sbrannen

@aooohan, we will discuss within the team whether we wish to add such a method.

Please note that the waiting-for-triage label signals that the team has not yet reached a decision on the issue.

Comment From: aooohan

@aooohan, we will discuss within the team whether we wish to add such a method.

Please note that the waiting-for-triage label signals that the team has not yet reached a decision on the issue.

Alright, thank you for your reply.

Comment From: rstoyanchev

We could maybe add a Consumer based overloaded variant modulesToInstall(Consumer<List<Module>> modules) that lets you customize the configured set rather than set the modules.

Comment From: AQS-DTheuke

Please keep it simple. We are already in a BuilderCustomizer.

Comment From: rstoyanchev

Note that the builder currently has two methods for modules, modules(List<Module> modules) and modulesToInstall(Module... modules), which have different semantics and are mutually exclusive. It's a bit more challenging to come up with something that is clearly aligned with one or the other. The proposed addModules sounds like it is aligned with modules but your example was for modulesToInstall so this is already ambiguous.

An overloaded method with a Consumer allows for a clear alignment with one vs the other method, and has the benefit of provoding full control over the list of modules, including where to insert, add, remove, etc.

Comment From: AQS-DTheuke

Please note that you always need two methods (unless you refactor the whole thing to avoid the findWellKnownModules in these methods, which might be a good idea). modules(...) is exclusive. modulesToInstall(...) is inclusive. And I am referring to both for consistency reasons, IMO its just somewhat redundant to always explicitly mention both.

Semantically, I wish to just add a module to the builder, so I would appreciate it, if there is a method for exactly that (similar to all the other existing methods). I explicitly added the two code examples in the issue description to show the difference in their implementation. Most of the other methods look like the serializer one (are adders not setters). IMO in the next major version of spring, this should be addressed further by renaming modules() to replaceModules(...) or setModules(...) and renaming addModules(...) to modules(...) so they behave the same as the other methods and don't disrupt developer expectations.

This is the workaround I currently use:

@Bean
// Custom error details
public Jackson2ObjectMapperBuilderCustomizer apiErrorCustomizer() {
    // We cannot use the module directly
    // because the module methods are only setters and not adders
    // builder#serializers adds the passed serializers instead
    return builder -> builder.serializers(ApiErrorModule.requiredSerializers())
                             .deserializers(ApiErrorModule.requiredDeserializers());
}

// --------------------- in another configuration class/artifact

@Bean
// "2000" -> "2000-01-01" <= x < "2001-01-01"
public Jackson2ObjectMapperBuilderCustomizer dateishRangeCustomizer() {
    return builder -> builder.serializers(DatishRangeModule.requiredSerializers())
                             .deserializers(DatishRangeModule.requiredDeserializers());
}

Comment From: rstoyanchev

We could consider making modules and modulesToInstall additive in 6.0 to align them with how other methods in the builder work which accept multiple registrations.

Comment From: rstoyanchev

Team Decision: we've decided to go with the Consumer based variants, which cover a wider range of cases without the potential for side effects from changing how the methods behave currently. This does mean an additional nested lambda, but still a reasonable compromise overall:

@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
    return builder -> builder.modules(list -> list.add(...));
}

As a result, the methods can be added immediately in the 5.3.x branch.