Skip to content

Fixes #29494: NPE in ServiceEntityResource.unmask when request has no connection - #29497

Open
ivnvMkhl wants to merge 4 commits into
open-metadata:mainfrom
ivnvMkhl:fix/service-unmask-null-connection
Open

Fixes #29494: NPE in ServiceEntityResource.unmask when request has no connection#29497
ivnvMkhl wants to merge 4 commits into
open-metadata:mainfrom
ivnvMkhl:fix/service-unmask-null-connection

Conversation

@ivnvMkhl

@ivnvMkhl ivnvMkhl commented Jun 25, 2026

Copy link
Copy Markdown

Description of Changes

Fixes #29494

ServiceEntityResource.unmask() checked originalService.getConnection() != null (the entity stored in DB) before calling unmaskServiceConnectionConfig, but did not check service.getConnection() != null (the incoming request body). When a PUT /api/v1/services/databaseServices request omits the connection field — a valid idempotent upsert pattern — the server threw a NullPointerException:

Cannot invoke "ServiceConnectionEntityInterface.getConfig()" because
the return value of "ServiceEntityInterface.getConnection()" is null

The fix adds the missing && service.getConnection() != null guard. When the incoming request has no connection, the unmask block is skipped and the existing connection in the DB is preserved unchanged.

Type of Change

  • Bug fix

High-level Design

N/A — single-condition null check addition.

Testing Coverage

  • Manual testing: reproduced by calling PUT /api/v1/services/databaseServices with {"name": "svc", "serviceType": "Hive"} on an existing service. Confirmed 200 response after the fix.

UI Evidence

Not applicable.

Checklist


Summary by Gitar

  • Bug fixes:
    • Resolved a NullPointerException in ServiceEntityResource during idempotent PUT requests when the connection field is omitted.
    • Added service.getConnection() != null guard to safely handle requests without connection details.
  • Performance optimization:
    • Fixed domain listing per-domain parent lookup N+1 issue impacting login performance at scale.

This will update automatically on new commits.

Greptile Summary

This PR fixes a NullPointerException in ServiceEntityResource.unmask() that occurred when a PUT /api/v1/services/databaseServices request omitted the connection field. The fix adds a service.getConnection() != null guard alongside the existing originalService.getConnection() != null check, so the unmask block is only entered when both sides have a connection.

  • ServiceEntityResource.java: Adds the missing null guard on the incoming service.getConnection() in unmask(), preventing an NPE when the request body omits connection.
  • DatabaseServiceResourceIT.java: Adds two integration tests covering the fixed scenarios — PUT without connection on a service that has one (verifies existing connection is preserved), and PUT without connection on a service that never had one (verifies no NPE).

Confidence Score: 5/5

Safe to merge — the change is a single, targeted null guard on a code path that had no protection for a null incoming connection.

The fix is minimal (one additional boolean condition), clearly correct, and directly addresses the root cause. Both code paths — a service that has an existing connection and one that never had a connection — are exercised by the new integration tests. No other behavior is altered.

No files require special attention.

Important Files Changed

Filename Overview
openmetadata-service/src/main/java/org/openmetadata/service/resources/services/ServiceEntityResource.java Adds service.getConnection() != null guard to unmask() — minimal, correct null check that prevents NPE on PUT requests with no connection.
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/DatabaseServiceResourceIT.java Adds two integration tests for the null-connection PUT path; both cases (service has existing connection, service has no connection) are covered with appropriate assertions.

Sequence Diagram

sequenceDiagram
    participant Client
    participant ServiceEntityResource
    participant Repository
    participant EntityMasker

    Client->>ServiceEntityResource: PUT /services/databaseServices (no connection field)
    ServiceEntityResource->>Repository: findByNameOrNull(fqn)
    Repository-->>ServiceEntityResource: originalService (with connection)
    
    Note over ServiceEntityResource: Before fix
    ServiceEntityResource->>ServiceEntityResource: "if originalService != null && originalService.getConnection() != null"
    ServiceEntityResource->>EntityMasker: unmaskServiceConnectionConfig(service.getConnection().getConfig(), ...)
    Note over ServiceEntityResource: service.getConnection() is null → NPE 💥

    Note over ServiceEntityResource: After fix
    ServiceEntityResource->>ServiceEntityResource: "if originalService != null && originalService.getConnection() != null && service.getConnection() != null"
    Note over ServiceEntityResource: guard fails → block skipped ✅
    ServiceEntityResource-->>Client: 200 OK
Loading

Reviews (6): Last reviewed commit: "Merge branch 'main' into fix/service-unm..." | Re-trigger Greptile

@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@github-actions

Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@TeddyCr TeddyCr added the safe to test Add this label to run secure Github workflows on PRs label Jun 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The Java checkstyle failed.

Please run mvn spotless:apply in the root of your repository and commit the changes to this PR.
You can also use pre-commit to automate the Java code formatting.

You can install the pre-commit hooks with make install_test precommit_install.

@github-actions

Copy link
Copy Markdown
Contributor

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 63%
63.42% (70688/111446) 45.9% (40625/88504) 47.81% (12369/25866)

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🔴 Playwright Results — 8 test failure(s), 4 pipeline/setup failure(s)

✅ 2883 passed · ❌ 8 failed · 🟡 11 flaky · ⏭️ 82 skipped

Pipeline and setup failures

  • Shard 3 did not upload a usable Playwright results artifact.
  • Shard 5 did not upload a usable Playwright results artifact.
  • Shard 3 did not upload its execution status.
  • Shard 5 did not upload its execution status.
Shard Passed Failed Flaky Skipped
🟡 Shard 1 438 0 2 16
✅ Shard 2 11 0 0 0
⛔ Shard 3
🔴 Shard 4 812 8 2 18
⛔ Shard 5
🟡 Shard 6 830 0 2 46
🟡 Shard 7 792 0 5 2

Genuine Failures (failed on all attempts)

Features/OntologyExplorerRdf.spec.ts › Turtle (.ttl) option appears in the export menu when RDF is enabled (shard 4)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed

Locator: getByText('Turtle (.ttl)', { exact: true })
Expected: visible
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toBeVisible" with timeout 15000ms�[22m
�[2m  - waiting for getByText('Turtle (.ttl)', { exact: true })�[22m

Features/OntologyExplorerRdf.spec.ts › RDF/XML (.rdf) option appears in the export menu when RDF is enabled (shard 4)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed

Locator: getByText('RDF/XML (.rdf)', { exact: true })
Expected: visible
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toBeVisible" with timeout 15000ms�[22m
�[2m  - waiting for getByText('RDF/XML (.rdf)', { exact: true })�[22m

Features/OntologyExplorerRdf.spec.ts › Turtle export triggers a .ttl file download (shard 4)
�[31mTest timeout of 60000ms exceeded.�[39m
Features/OntologyExplorerRdf.spec.ts › RDF/XML export triggers a .rdf file download (shard 4)
�[31mTest timeout of 60000ms exceeded.�[39m
Features/OntologyExplorerRdf.spec.ts › term Relations Graph requests /rdf/glossary/graph scoped to the selected term (glossaryTermId) when RDF is enabled (shard 4)
�[31mTest timeout of 60000ms exceeded.�[39m
Features/OntologyExplorerRdf.spec.ts › glossary Relations Graph calls /rdf/glossary/graph when RDF is enabled and renders nodes from the response (shard 4)
Error: GET /rdf/glossary/graph must be called on the glossary Relations Graph when RDF is enabled

�[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBe�[2m(�[22m�[32mexpected�[39m�[2m) // Object.is equality�[22m

Expected: �[32mtrue�[39m
Received: �[31mfalse�[39m
Features/ServiceAgentsPauseResume.spec.ts › should pause an enabled agent and offer resume afterwards (shard 4)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed

Locator: getByTestId('resume-button')
Expected: visible
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toBeVisible" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('resume-button')�[22m

Features/ServiceAgentsPauseResume.spec.ts › should resume a paused agent and offer pause afterwards (shard 4)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed

Locator: getByTestId('resume-button')
Expected: visible
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toBeVisible" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('resume-button')�[22m

🟡 11 flaky test(s) (passed on retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab IS visible for supported type: chart (shard 1, 1 retry)
  • Flow/SearchRBAC.spec.ts › the browse tree only shows the asset-type categories a user can access (shard 1, 1 retry)
  • Features/MetricBulkImportExportEdit.spec.ts › MetricListPage unchecking header checkbox clears the selection bar (shard 4, 1 retry)
  • Features/SearchSeparation/SearchSeparationSuite.ts › SearchIndexApp recreate reindex preserves searchable separation (shard 4, 1 retry)
  • Pages/Entity.spec.ts › Certification Add Remove (shard 6, 2 retries)
  • Pages/ExploreBrowse.spec.ts › service type drill-down disables unrelated roots and query-panel Clear resets it (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify Impact Analysis service filter selection (shard 7, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab is NOT visible for pipelineService in platform lineage (shard 7, 1 retry)
  • Pages/Lineage/LineageRightPanel.spec.ts › Verify custom properties tab is NOT visible for apiService in platform lineage (shard 7, 1 retry)
  • Pages/ServiceEntity.spec.ts › Announcement create, edit & delete (shard 7, 1 retry)
  • Pages/TestSuite.spec.ts › Logical TestSuite (shard 7, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

@sonarqubecloud

Copy link
Copy Markdown

@sonarqubecloud

Copy link
Copy Markdown

Михаил Иванов added 2 commits July 20, 2026 20:36
…equest has no connection

unmask() guarded against originalService.getConnection() == null but not
against service.getConnection() == null (the incoming request). A PUT with no
connection field (idempotent ensure-service-exists) threw NullPointerException
when the service already existed. Added the missing null check.
@ivnvMkhl
ivnvMkhl force-pushed the fix/service-unmask-null-connection branch from 8491f16 to de22c83 Compare July 20, 2026 17:37
@harshach

Copy link
Copy Markdown
Collaborator

@ivnvMkhl please add a integration test to reproduce this issue

Per review feedback (harshach): put_updateWithoutConnection_preservesExistingConnection_200
drives a raw PUT /v1/services/databaseServices with no connection field
against a service that already has one, hitting ServiceEntityResource.unmask()
directly. Fails with a 500 (NPE) without the null-check fix; asserts a 200
and that the pre-existing connection is left untouched.

Also adds put_updateWithoutConnection_whenNoExistingConnection_200 as a
smoke check on the neighboring branch (no connection on either side),
which was already guarded before this fix.
@ivnvMkhl

Copy link
Copy Markdown
Author

@harshach Added an integration test — put_updateWithoutConnection_preservesExistingConnection_200 in DatabaseServiceResourceIT.java, which reproduces the NPE via a raw PUT with no connection field against a service that already has one, and asserts the existing connection is preserved. Also added a smoke test for the neighboring no-connection-on-either-side case.

@gitar-bot

gitar-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown
Code Review ✅ Approved

Prevents a NullPointerException in ServiceEntityResource by adding a guard for missing connection fields in idempotent PUT requests. No issues found.

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@github-actions

Copy link
Copy Markdown
Contributor

This PR has had no activity for 30 days and will be closed in 7 days if no further activity occurs.
Feel free to reopen it if you'd like to continue working on it.

@github-actions github-actions Bot added the Stale label Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test Add this label to run secure Github workflows on PRs Stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NPE in ServiceEntityResource.unmask when PUT service request has no connection field

3 participants