diff --git a/lib/util/http/index.spec.ts b/lib/util/http/index.spec.ts
index 7e8668154cf204f22f394f6d85e8f0ef5239c7c5..49f9f1aff03ec9cfcc744c9f31a35ada6e329100 100644
--- a/lib/util/http/index.spec.ts
+++ b/lib/util/http/index.spec.ts
@@ -1,4 +1,4 @@
-import { z } from 'zod';
+import { ZodError, z } from 'zod';
 import * as httpMock from '../../../test/http-mock';
 import { logger } from '../../../test/util';
 import {
@@ -10,7 +10,7 @@ import * as hostRules from '../host-rules';
 import * as queue from './queue';
 import * as throttle from './throttle';
 import type { HttpResponse } from './types';
-import { Http } from '.';
+import { Http, HttpError } from '.';
 
 const baseUrl = 'http://renovate.com';
 
@@ -365,6 +365,47 @@ describe('util/http/index', () => {
       });
     });
 
+    describe('getJsonSafe', () => {
+      it('uses schema for response body', async () => {
+        httpMock
+          .scope('http://example.com')
+          .get('/')
+          .reply(200, JSON.stringify({ x: 2, y: 2 }));
+
+        const { val, err } = await http
+          .getJsonSafe('http://example.com', SomeSchema)
+          .unwrap();
+
+        expect(val).toBe('2 + 2 = 4');
+        expect(err).toBeUndefined();
+      });
+
+      it('returns schema error result', async () => {
+        httpMock
+          .scope('http://example.com')
+          .get('/')
+          .reply(200, JSON.stringify({ x: '2', y: '2' }));
+
+        const { val, err } = await http
+          .getJsonSafe('http://example.com', SomeSchema)
+          .unwrap();
+
+        expect(val).toBeUndefined();
+        expect(err).toBeInstanceOf(ZodError);
+      });
+
+      it('returns error result', async () => {
+        httpMock.scope('http://example.com').get('/').replyWithError('unknown');
+
+        const { val, err } = await http
+          .getJsonSafe('http://example.com', SomeSchema)
+          .unwrap();
+
+        expect(val).toBeUndefined();
+        expect(err).toBeInstanceOf(HttpError);
+      });
+    });
+
     describe('postJson', () => {
       it('uses schema for response body', async () => {
         httpMock
diff --git a/lib/util/http/index.ts b/lib/util/http/index.ts
index ffaadfcf40f42fb4db98d26eb8d4a0abe64a052f..1846a64902de32bb87a0b6c7952fa1660811b195 100644
--- a/lib/util/http/index.ts
+++ b/lib/util/http/index.ts
@@ -2,13 +2,14 @@ import merge from 'deepmerge';
 import got, { Options, RequestError } from 'got';
 import hasha from 'hasha';
 import type { SetRequired } from 'type-fest';
-import { infer as Infer, ZodType } from 'zod';
+import { infer as Infer, type ZodError, ZodType } from 'zod';
 import { HOST_DISABLED } from '../../constants/error-messages';
 import { pkg } from '../../expose.cjs';
 import { logger } from '../../logger';
 import { ExternalHostError } from '../../types/errors/external-host-error';
 import * as memCache from '../cache/memory';
 import { clone } from '../clone';
+import { type AsyncResult, Result } from '../result';
 import { resolveBaseUrl } from '../url';
 import { applyAuthorization, removeAuthorization } from './auth';
 import { hooks } from './hooks';
@@ -29,6 +30,9 @@ import './legacy';
 
 export { RequestError as HttpError };
 
+export class EmptyResultError extends Error {}
+export type SafeJsonError = RequestError | ZodError | EmptyResultError;
+
 type JsonArgs<
   Opts extends HttpOptions & HttpRequestOptions<ResT>,
   ResT = unknown,
@@ -348,6 +352,32 @@ export class Http<Opts extends HttpOptions = HttpOptions> {
     return this.requestJson<ResT>('get', args);
   }
 
+  getJsonSafe<
+    ResT extends NonNullable<unknown>,
+    Schema extends ZodType<ResT> = ZodType<ResT>
+  >(url: string, schema: Schema): AsyncResult<Infer<Schema>, SafeJsonError>;
+  getJsonSafe<
+    ResT extends NonNullable<unknown>,
+    Schema extends ZodType<ResT> = ZodType<ResT>
+  >(
+    url: string,
+    options: Opts & HttpRequestOptions<Infer<Schema>>,
+    schema: Schema
+  ): AsyncResult<Infer<Schema>, SafeJsonError>;
+  getJsonSafe<
+    ResT extends NonNullable<unknown>,
+    Schema extends ZodType<ResT> = ZodType<ResT>
+  >(
+    arg1: string,
+    arg2?: (Opts & HttpRequestOptions<ResT>) | Schema,
+    arg3?: Schema
+  ): AsyncResult<ResT, SafeJsonError> {
+    const args = this.resolveArgs<ResT>(arg1, arg2, arg3);
+    return Result.wrap(this.requestJson<ResT>('get', args)).transform(
+      (response) => response.body
+    );
+  }
+
   headJson(url: string, httpOptions?: Opts): Promise<HttpResponse<never>> {
     return this.requestJson<never>('head', { url, httpOptions });
   }