Skip to content

Add Builder class for MerchantRecipe - #14127

Open
Andre601 wants to merge 4 commits into
PaperMC:mainfrom
Andre601:feat/add-merchantrecipe-builder
Open

Add Builder class for MerchantRecipe#14127
Andre601 wants to merge 4 commits into
PaperMC:mainfrom
Andre601:feat/add-merchantrecipe-builder

Conversation

@Andre601

@Andre601 Andre601 commented Jul 30, 2026

Copy link
Copy Markdown

This Pull request adds a Builder class to the MerchantRecipe class.

Motivation

The result item in the MerchantRecipe class is unmodifiable and the only way to change it, is by creating a new instance of the class.
This is okay for situations where you want to modify the values like experience gain, multiplier, etc. but for situations where you just want to change the result item of an existing MerchantRecipe, this situation becomes tedious, as you now need to apply every single value from the original MerchantRecipe, to the new one through the constructor.

This Builder class should help, as there is now a method in MerchantRecipe called builder() that returns a new Builder class using the MerchantRecipe's settings and allowing you to modify them.

Notes

I'm currently debugging an issue where the MerchantRecipe modification in a VillagerAcquireTradeEvent results in a villager not getting any trades to share.
The event is fired regularely, but interacting with the villager ends up in them shaking the head as if they have no trades.

I'm unsure of the cause here, but it may be an oversight on my end.

Here's the code used to test through the test-plugin:

    @EventHandler
    public void onVillager(VillagerAcquireTradeEvent event) {
        getLogger().info("Event fired!");
        MerchantRecipe.Builder builder = event.getRecipe().builder();
        
        builder.setIngredients(List.of(ItemStack.of(Material.DIRT, 64)))
            .setResult(ItemStack.of(Material.DIAMOND));
        
        event.setRecipe(builder.build());
    }

@Andre601
Andre601 requested a review from a team as a code owner July 30, 2026 00:20
@github-project-automation github-project-automation Bot moved this to Awaiting review in Paper PR Queue Jul 30, 2026
Comment thread paper-api/src/main/java/org/bukkit/inventory/MerchantRecipe.java Outdated
* @param result The ItemStack to use as the result for the MerchantRecipe.
* @param maxUses Number of max uses for this MerchantRecipe
*/
public Builder(ItemStack result, int maxUses) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a reason we are exposing these constructors in favor of static API methods? I actually think the normal builder method on MerchantRecipe that isn't static is also okay, but this feels like a loose end that could be tied up pretty easily.

@Andre601 Andre601 Aug 2, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I can make the constructor private and expose a static builder method.

Would it be good to have a builder(ItemStack, int) and a builder(MerchantRecipe) or should we only allow builder(ItemStack, int) and have a builder from a MerchantRecipe through its builder method?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think the latter would be best

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would rename the builder method to #toBuilder to match the data component builder and probably obsolete a bunch of constructors on MerchantRecipe (the builder being preferred).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I would rename the builder method to #toBuilder to match the data component builder and probably obsolete a bunch of constructors on MerchantRecipe (the builder being preferred).

If by obsolete you mean mark them as deprecated, as I don't think a breaking change in the form of removing constructors should be considered here. Unless Paper team would be fine with such a change.

@Y2Kwastaken Y2Kwastaken Aug 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If by obsolete you mean mark them as deprecated

Obselete is a jetbrains annotation namely ApiStatus.Obselete. I agree with Lulu here that's probably a smart addition.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Which constructors would be best to obsolete?
The ones with fewest, or most arguments?

* @return New MerchantRecipe class.
*/
public MerchantRecipe build() {
return new MerchantRecipe(result, uses, maxUses, experienceReward,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is more of a genuine question rather than a review point. Are there "required" entries for a MerchantRecipe? Also would it do any good to enforce those here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Result and max uses are required given the Constructor with fewest parameters for MerchantRecipe has those set.

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

Labels

None yet

Projects

Status: Awaiting review

Development

Successfully merging this pull request may close these issues.

5 participants