Skip to content

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

Open
Jakubk15 wants to merge 6 commits into
masterfrom
fix/issue-221-collection-gui-npe
Open

GH-221 NPE in CollectionGui from null parcel content items#223
Jakubk15 wants to merge 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Egi4whB4fVKQL3XH4PpkX9
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