Skip to content

GH-221 NPE in CollectionGui from null parcel content items#223

Merged
Jakubk15 merged 6 commits into
masterfrom
fix/issue-221-collection-gui-npe
Jul 2, 2026
Merged

GH-221 NPE in CollectionGui from null parcel content items#223
Jakubk15 merged 6 commits into
masterfrom
fix/issue-221-collection-gui-npe

Conversation

@Jakubk15

Copy link
Copy Markdown
Member

Problem

Closes #221. Opening the parcel collection GUI threw a NullPointerException:

Cannot invoke "org.bukkit.inventory.ItemStack.getType()" because "itemStack" is null
  at ...CollectionGui.lambda$createParcelItemAsync$11(CollectionGui.java:133)

CollectionGui.createParcelItemAsync iterates a parcel's stored content list and calls itemStack.getType() with no null guard. The list contained a literal null element.

Root cause

The content list survives a serialize → DB → deserialize round-trip through ItemStackPersister, which was configured with legacy (Spigot map) ItemStack serialization. Legacy serialization drops empty/air stacks to null. Empty/air inventory slots can slip past the GUI write filters (if (item == null) continue; does not catch isEmpty()/air), get persisted, and come back as null list elements — which then NPE on read.

Fix

Two layers:

  1. Defensive guard at the model boundary (ParcelContent): the record's compact constructor now strips null and isEmpty() stacks and exposes an immutable list. Every read and write path funnels through ParcelContent, so this heals already-corrupted rows on read and prevents bad writes.
  2. Upstream fix (ItemStackPersister): stop using legacy serialization. The jackson-bukkit Paper module defaults to an NBT byte-array format (ItemStack#serializeAsBytes) that round-trips empties safely. Its deserializer auto-detects the format, so previously stored legacy-format rows still read correctly and are rewritten in the new format on next save. This is the configuration the library docs explicitly recommend for paper plugins ("make sure that you are not using legacy serialization").

Tests

  • New ParcelContentTest covers null-stripping and empty-stripping (written test-first, watched fail, then fixed).
  • Added Mockito as a test dependency: paper-api ItemStacks cannot be constructed in plain unit tests (they need a running server's RegistryAccess), so ItemStack is mocked.

Verification notes

./gradlew compileJava test passes. The serialization round-trip itself is not covered by automated tests: paper-api ItemStacks require a running server and the existing integration tests are Docker-gated, so the format change relies on the library's documented backward-compatible auto-detection. Worth a manual smoke test (send a parcel, collect it) on a real server before release.

🤖 Generated with Claude Code

Jakubk15 and others added 2 commits June 22, 2026 18:59
Empty/air inventory slots could slip past the GUI write filters and
round-trip through the persister as null elements, causing a
NullPointerException when CollectionGui read itemStack.getType() on a
collected parcel.

Normalize the item list in the ParcelContent record's compact
constructor so null and empty stacks are dropped and the exposed list is
immutable. This is the single chokepoint every read and write path goes
through, so it heals already-corrupted rows on read and prevents bad
writes.

Adds Mockito to mock ItemStack in unit tests, since paper-api ItemStacks
cannot be constructed without a running server (RegistryAccess).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01R4YqhTV42FvN95HanC7XJc
Legacy (Spigot map) ItemStack serialization drops empty/air stacks to
null, the upstream cause of the issue #221 NPE. The jackson-bukkit Paper
module defaults to an NBT byte-array format (ItemStack#serializeAsBytes)
that round-trips empties safely, and its deserializer auto-detects the
format, so previously stored legacy-format rows still read correctly and
are rewritten in the new format on next save.

The ParcelContent null/empty guard remains as defense in depth.

Note: the serialization round-trip cannot be exercised by the existing
test harness (paper-api ItemStacks require a running server, and the
integration tests are Docker-gated), so this relies on the library's
documented backward-compatible auto-detection.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01R4YqhTV42FvN95HanC7XJc
@Jakubk15 Jakubk15 changed the title fix: NPE in CollectionGui from null parcel content items (#221) GH-221 NPE in CollectionGui from null parcel content items Jun 22, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request addresses issue #221 by adding a guard in the ParcelContent constructor to filter out null and empty item stacks, and by updating ItemStackPersister to use default Paper serialization instead of legacy serialization. Additionally, Mockito was added to the test dependencies and new unit tests were introduced. The reviewer's feedback suggests optimizing the ParcelContent constructor to avoid stream overhead when the list is empty, and adding a unit test to verify that a null items list is handled correctly.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/main/java/com/eternalcode/parcellockers/content/ParcelContent.java Outdated
Comment on lines +34 to +37
ParcelContent content = new ParcelContent(UUID.randomUUID(), List.of(stone, air));

assertEquals(List.of(stone), content.items());
}

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.

medium

It would be beneficial to add a unit test to verify that the compact constructor correctly handles a null items list by defaulting to an empty list, ensuring full coverage of the defensive guard.

        ParcelContent content = new ParcelContent(UUID.randomUUID(), List.of(stone, air));

        assertEquals(List.of(stone), content.items());
    }

    @Test
    void handlesNullItemsList() {
        ParcelContent content = new ParcelContent(UUID.randomUUID(), null);

        assertEquals(List.of(), content.items());
    }

@Jakubk15

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Jun 22, 2026

Copy link
Copy Markdown

Claude finished @Jakubk15's task in 2s —— View job


I'll analyze this and get back to you.

Jakubk15 and others added 2 commits June 22, 2026 19:05
…ent.java

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Adds a case verifying the compact constructor defaults a null items list
to an empty list, closing the coverage gap on the defensive guard.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01R4YqhTV42FvN95HanC7XJc
@Jakubk15 Jakubk15 added the ✅ needs testing Issue needs verification. label Jun 22, 2026
Jakubk15 and others added 2 commits June 22, 2026 20:54
A parcel description may legitimately be null (SendingGui stores a blank
description as null). AdminParcelEditGui.button() passed the value straight
to String.replace(), which throws NPE when the replacement is null, crashing
the admin edit GUI on InventoryClickEvent.

Coerce null placeholder values to an empty string via a testable
nullToEmpty() helper and cover it with unit tests.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Egi4whB4fVKQL3XH4PpkX9
@Jakubk15 Jakubk15 merged commit 2ee92cd into master Jul 2, 2026
1 check passed
@Jakubk15 Jakubk15 deleted the fix/issue-221-collection-gui-npe branch July 2, 2026 20:07
@Jakubk15

Jakubk15 commented Jul 4, 2026

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Jul 4, 2026

Copy link
Copy Markdown

Claude finished @Jakubk15's task in 1m 49s —— View job


Reviewing PR #223

  • Gather context (PR diff, description, existing review comments)
  • Review ParcelContent.java changes
  • Review ItemStackPersister.java changes
  • Review AdminParcelEditGui.java changes
  • Review new tests
  • Post final review

Review

Solid fix, and both of Gemini's suggestions were already incorporated in the final version I checked out (ParcelContent.java:14-18 short-circuits on null/empty without building a stream, and ParcelContentTest.java:39-44 adds handlesNullItemsList). No further action needed on those two points.

What's good:

  • ParcelContent's compact constructor (src/main/java/com/eternalcode/parcellockers/content/ParcelContent.java:10-19) is the right layer for this guard — I traced both the read path (ParcelContentTable.toParcelContent()) and write path (ParcelContentTable.from(), ParcelContentManager.java:45,55, ParcelServiceImpl.java:123) and every one funnels through this constructor, so the filtering is genuinely universal rather than patched at one call site.
  • Confirmed the original NPE site (CollectionGui.java:133-137, iterating itemStack.getType()) now consumes content.items(), which can no longer contain nulls.
  • Switching ItemStackPersister off legacy serialization (ItemStackPersister.java:24-26) is the correct root-cause fix per the jackson-bukkit docs, and the "auto-detects old format on read" behavior means existing DB rows aren't broken by the change.
  • New AdminParcelEditGuiTest covering nullToEmpty is a reasonable, low-cost addition given AdminParcelEditGui.java:98-100 passes a nullable parcel.description() into String#replace.

Notes / residual risk (not blockers, already called out in the PR description):

  • The serialization format switch itself isn't covered by an automated test — understandable given paper-api ItemStack needs a live server, but worth the manual smoke test (send → collect a parcel) mentioned in the PR description before this reaches a release build, particularly to confirm the auto-detecting deserializer really does transparently upgrade rows written in the old legacy format.
  • ParcelContentTest mocks ItemStack rather than using a real instance; that's a reasonable trade-off for a plain unit test (real ItemStacks need RegistryAccess), and the behavior under test (null/empty filtering) doesn't depend on real Bukkit internals, so this is fine as-is.

No changes requested — this PR is already merged and the follow-up review comments were addressed in the final commits.

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

Labels

✅ needs testing Issue needs verification.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exception in future due to NPE

1 participant