Repository: gchq/Gaffer (java)

Issue:
JobTracker tracks all operations, not just jobs
The documentation of Job, and the general title/JavaDoc of JobTracker indicates that, when enabled on a store, the JobTracker should track only Jobs.

However, inspection of Store.java (see https://github.com/gchq/Gaffer/blob/1fb6823082ef9dd88f65fab33ac1541e8a2a76cc/core/store/src/main/java/uk/gov/gchq/gaffer/store/Store.java#L389  and https://github.com/gchq/Gaffer/blob/1fb6823082ef9dd88f65fab33ac1541e8a2a76cc/core/store/src/main/java/uk/gov/gchq/gaffer/store/Store.java#L924) indicates that, when the JobTracker is enabled, any and every operation executed on the store is tracked as if it was a Job.

I suggest this is a defect, and that regular non-job operations should not be tracked by the JobTracker.  

If this _is_, in fact, intended behaviour, then I suggest the documentation and Javadoc should be updated, and maybe the name of JobTracker changed to OperationTracker, as I'd argue the existing behaviour is surprising given the name of the class and documentation.

Impacts of this defect include:

1. Performance degradation when running multiple fast operations caused by multiple writes to the cache for every operation
2. Performance degradation when instantiating a Store (see the initialise method in Store.java), as initialising a store with the job tracker enabled requires iterating through everything tracked by the JobTracker (even jobs for other stores)
3. In an environment where the JobTracker cache has a maximum size; early expiry of 'real' jobs as they are forced out of the cache early
4. Data being persisted to the cache that the user might not expect

As always, happy to submit a PR (I think the solution to this issue is quite straightforward) if the maintainers agree that this is indeed a defect.

Thanks,

P.S.  This issue has partially blocked the testing of gh-2457

Pull request:
Gh-2478: JobTracker tracks all operations, not just jobs
Removes the job tracker from the main execute and any returned headers.

# Related issue

- Resolve #2478

Patch:
diff --git a/core/store/src/main/java/uk/gov/gchq/gaffer/store/Store.java b/core/store/src/main/java/uk/gov/gchq/gaffer/store/Store.java
index d153548bac..40d37b067a 100644
--- a/core/store/src/main/java/uk/gov/gchq/gaffer/store/Store.java
+++ b/core/store/src/main/java/uk/gov/gchq/gaffer/store/Store.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2024 Crown Copyright
+ * Copyright 2016-2025 Crown Copyright
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -386,15 +386,7 @@ public abstract class Store {
     }
 
     protected <O> O execute(final OperationChain<O> operation, final Context context) throws OperationException {
-        try {
-            addOrUpdateJobDetail(operation, context, null, JobStatus.RUNNING);
-            final O result = (O) handleOperation(operation, context);
-            addOrUpdateJobDetail(operation, context, null, JobStatus.FINISHED);
-            return result;
-        } catch (final Throwable t) {
-            addOrUpdateJobDetail(operation, context, t.getMessage(), JobStatus.FAILED);
-            throw t;
-        }
+        return (O) handleOperation(operation, context);
     }
 
     /**
diff --git a/rest-api/common-rest/src/main/java/uk/gov/gchq/gaffer/rest/service/v2/AbstractOperationService.java b/rest-api/common-rest/src/main/java/uk/gov/gchq/gaffer/rest/service/v2/AbstractOperationService.java
index 36269935a9..f8aeb5c1ec 100644
--- a/rest-api/common-rest/src/main/java/uk/gov/gchq/gaffer/rest/service/v2/AbstractOperationService.java
+++ b/rest-api/common-rest/src/main/java/uk/gov/gchq/gaffer/rest/service/v2/AbstractOperationService.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2023 Crown Copyright
+ * Copyright 2020-2025 Crown Copyright
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
 package uk.gov.gchq.gaffer.rest.service.v2;
 
 import uk.gov.gchq.gaffer.commonutil.CloseableUtil;
-import uk.gov.gchq.gaffer.commonutil.pair.Pair;
 import uk.gov.gchq.gaffer.core.exception.GafferRuntimeException;
 import uk.gov.gchq.gaffer.core.exception.Status;
 import uk.gov.gchq.gaffer.graph.GraphRequest;
@@ -97,7 +96,7 @@ public abstract class AbstractOperationService {
     }
 
     @SuppressWarnings({"ThrowFromFinallyBlock", "PMD.UseTryWithResources"})
-    protected <O> Pair<O, String> _execute(final Operation operation, final Context context) {
+    protected <O> O _execute(final Operation operation, final Context context) {
 
         OperationChain<O> opChain = (OperationChain<O>) OperationChain.wrap(operation);
 
@@ -119,7 +118,7 @@ public abstract class AbstractOperationService {
             }
         }
 
-        return new Pair<>(result.getResult(), result.getContext().getJobId());
+        return result.getResult();
     }
 
     protected Operation generateExampleJson(final Class<? extends Operation> opClass) throws IllegalAccessException, InstantiationException {
diff --git a/rest-api/core-rest/src/main/java/uk/gov/gchq/gaffer/rest/service/v2/IOperationServiceV2.java b/rest-api/core-rest/src/main/java/uk/gov/gchq/gaffer/rest/service/v2/IOperationServiceV2.java
index c0839f6631..4156138eb4 100644
--- a/rest-api/core-rest/src/main/java/uk/gov/gchq/gaffer/rest/service/v2/IOperationServiceV2.java
+++ b/rest-api/core-rest/src/main/java/uk/gov/gchq/gaffer/rest/service/v2/IOperationServiceV2.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2020 Crown Copyright
+ * Copyright 2016-2025 Crown Copyright
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -42,8 +42,6 @@ import static uk.gov.gchq.gaffer.rest.ServiceConstants.FORBIDDEN;
 import static uk.gov.gchq.gaffer.rest.ServiceConstants.GAFFER_MEDIA_TYPE_HEADER;
 import static uk.gov.gchq.gaffer.rest.ServiceConstants.GAFFER_MEDIA_TYPE_HEADER_DESCRIPTION;
 import static uk.gov.gchq.gaffer.rest.ServiceConstants.INTERNAL_SERVER_ERROR;
-import static uk.gov.gchq.gaffer.rest.ServiceConstants.JOB_ID_HEADER;
-import static uk.gov.gchq.gaffer.rest.ServiceConstants.JOB_ID_HEADER_DESCRIPTION;
 import static uk.gov.gchq.gaffer.rest.ServiceConstants.OK;
 import static uk.gov.gchq.gaffer.rest.ServiceConstants.OPERATION_NOT_FOUND;
 import static uk.gov.gchq.gaffer.rest.ServiceConstants.OPERATION_NOT_IMPLEMENTED;
@@ -91,7 +89,6 @@ public interface IOperationServiceV2 {
             produces = (APPLICATION_JSON + "," + TEXT_PLAIN),
             response = Object.class,
             responseHeaders = {
-                    @ResponseHeader(name = JOB_ID_HEADER, description = JOB_ID_HEADER_DESCRIPTION),
                     @ResponseHeader(name = GAFFER_MEDIA_TYPE_HEADER, description = GAFFER_MEDIA_TYPE_HEADER_DESCRIPTION)
             })
     @ApiResponses(value = {@ApiResponse(code = 200, message = OK, response = Object.class),
diff --git a/rest-api/core-rest/src/main/java/uk/gov/gchq/gaffer/rest/service/v2/OperationServiceV2.java b/rest-api/core-rest/src/main/java/uk/gov/gchq/gaffer/rest/service/v2/OperationServiceV2.java
index c18011f685..c07467c5f8 100644
--- a/rest-api/core-rest/src/main/java/uk/gov/gchq/gaffer/rest/service/v2/OperationServiceV2.java
+++ b/rest-api/core-rest/src/main/java/uk/gov/gchq/gaffer/rest/service/v2/OperationServiceV2.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2017-2023 Crown Copyright
+ * Copyright 2017-2025 Crown Copyright
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,7 +24,6 @@ import org.slf4j.LoggerFactory;
 
 import uk.gov.gchq.gaffer.commonutil.CloseableUtil;
 import uk.gov.gchq.gaffer.commonutil.exception.UnauthorisedException;
-import uk.gov.gchq.gaffer.commonutil.pair.Pair;
 import uk.gov.gchq.gaffer.core.exception.Error;
 import uk.gov.gchq.gaffer.core.exception.Status;
 import uk.gov.gchq.gaffer.operation.Operation;
@@ -45,7 +44,6 @@ import static javax.ws.rs.core.Response.Status.NOT_FOUND;
 import static uk.gov.gchq.gaffer.jsonserialisation.JSONSerialiser.createDefaultMapper;
 import static uk.gov.gchq.gaffer.rest.ServiceConstants.GAFFER_MEDIA_TYPE;
 import static uk.gov.gchq.gaffer.rest.ServiceConstants.GAFFER_MEDIA_TYPE_HEADER;
-import static uk.gov.gchq.gaffer.rest.ServiceConstants.JOB_ID_HEADER;
 
 /**
  * An implementation of {@link IOperationServiceV2}. By default it will use a singleton
@@ -85,10 +83,9 @@ public class OperationServiceV2 extends AbstractOperationService implements IOpe
 
     @Override
     public Response execute(final Operation operation) {
-        final Pair<Object, String> resultAndJobId = _execute(operation, userFactory.createContext());
-        return Response.ok(resultAndJobId.getFirst())
+        final Object result = _execute(operation, userFactory.createContext());
+        return Response.ok(result)
                 .header(GAFFER_MEDIA_TYPE_HEADER, GAFFER_MEDIA_TYPE)
-                .header(JOB_ID_HEADER, resultAndJobId.getSecond())
                 .build();
     }
 
@@ -109,7 +106,7 @@ public class OperationServiceV2 extends AbstractOperationService implements IOpe
         // create thread to write chunks to the chunked output object
         Thread thread = new Thread(() -> {
             try {
-                final Object result = _execute(opChain, context).getFirst();
+                final Object result = _execute(opChain, context);
                 chunkResult(result, output);
             } catch (final Exception e) {
                 throw new RuntimeException(e);
diff --git a/rest-api/spring-rest/src/main/java/uk/gov/gchq/gaffer/rest/controller/OperationController.java b/rest-api/spring-rest/src/main/java/uk/gov/gchq/gaffer/rest/controller/OperationController.java
index 0d6e3ce024..529f434b6d 100644
--- a/rest-api/spring-rest/src/main/java/uk/gov/gchq/gaffer/rest/controller/OperationController.java
+++ b/rest-api/spring-rest/src/main/java/uk/gov/gchq/gaffer/rest/controller/OperationController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2024 Crown Copyright
+ * Copyright 2020-2025 Crown Copyright
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,7 +36,6 @@ import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
 
 import uk.gov.gchq.gaffer.commonutil.CloseableUtil;
-import uk.gov.gchq.gaffer.commonutil.pair.Pair;
 import uk.gov.gchq.gaffer.core.exception.GafferRuntimeException;
 import uk.gov.gchq.gaffer.core.exception.Status;
 import uk.gov.gchq.gaffer.operation.Operation;
@@ -56,7 +55,6 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
 import static uk.gov.gchq.gaffer.jsonserialisation.JSONSerialiser.createDefaultMapper;
 import static uk.gov.gchq.gaffer.rest.ServiceConstants.GAFFER_MEDIA_TYPE;
 import static uk.gov.gchq.gaffer.rest.ServiceConstants.GAFFER_MEDIA_TYPE_HEADER;
-import static uk.gov.gchq.gaffer.rest.ServiceConstants.JOB_ID_HEADER;
 
 @RestController
 @Tag(name = "operations")
@@ -211,11 +209,10 @@ public class OperationController extends AbstractOperationService {
             }))
             @RequestBody final Operation operation) {
         userFactory.setHttpHeaders(httpHeaders);
-        final Pair<Object, String> resultAndJobId = _execute(operation, userFactory.createContext());
+        final Object result = _execute(operation, userFactory.createContext());
         return ResponseEntity.ok()
                 .header(GAFFER_MEDIA_TYPE_HEADER, GAFFER_MEDIA_TYPE)
-                .header(JOB_ID_HEADER, resultAndJobId.getSecond())
-                .body(resultAndJobId.getFirst());
+                .body(result);
     }
 
     @PostMapping(path = "/execute/chunked", consumes = APPLICATION_JSON_VALUE, produces = APPLICATION_JSON_VALUE)
@@ -227,8 +224,7 @@ public class OperationController extends AbstractOperationService {
         userFactory.setHttpHeaders(httpHeaders);
         final StreamingResponseBody responseBody = response -> {
             try {
-                final Pair<Object, String> resultAndJobId = _execute(operation, userFactory.createContext());
-                final Object result = resultAndJobId.getFirst();
+                final Object result = _execute(operation, userFactory.createContext());
                 if (result instanceof Iterable) {
                     final Iterable itr = (Iterable) result;
                     try {


Test patch:
diff --git a/core/store/src/test/java/uk/gov/gchq/gaffer/store/StoreTest.java b/core/store/src/test/java/uk/gov/gchq/gaffer/store/StoreTest.java
index c01e7241d8..27ae65325b 100644
--- a/core/store/src/test/java/uk/gov/gchq/gaffer/store/StoreTest.java
+++ b/core/store/src/test/java/uk/gov/gchq/gaffer/store/StoreTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2024 Crown Copyright
+ * Copyright 2016-2025 Crown Copyright
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -31,7 +31,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
 import uk.gov.gchq.gaffer.cache.CacheServiceLoader;
 import uk.gov.gchq.gaffer.cache.ICache;
 import uk.gov.gchq.gaffer.cache.ICacheService;
-import uk.gov.gchq.gaffer.cache.exception.CacheOperationException;
 import uk.gov.gchq.gaffer.cache.impl.HashMapCacheService;
 import uk.gov.gchq.gaffer.commonutil.TestGroups;
 import uk.gov.gchq.gaffer.commonutil.TestPropertyNames;
@@ -164,12 +163,12 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.BDDMockito.given;
-import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static uk.gov.gchq.gaffer.jobtracker.JobTracker.JOB_TRACKER_CACHE_SERVICE_NAME;
 import static uk.gov.gchq.gaffer.store.StoreTrait.INGEST_AGGREGATION;
 import static uk.gov.gchq.gaffer.store.StoreTrait.ORDERED;
@@ -257,12 +256,10 @@ public class StoreTest {
     }
 
     @Test
-    public void shouldExecuteOperationWhenJobTrackerCacheIsBroken(@Mock final StoreProperties storeProperties) throws Exception {
+    void shouldNotCreateJobWhenExecutingOperation(@Mock final StoreProperties storeProperties) throws Exception {
         // Given
         ICache<Object, Object> mockICache = Mockito.mock(ICache.class);
-        doThrow(new CacheOperationException("Stubbed class")).when(mockICache).put(any(), any());
         ICacheService mockICacheService = Mockito.spy(ICacheService.class);
-        given(mockICacheService.getCache(any())).willReturn(mockICache);
 
         Field field = CacheServiceLoader.class.getDeclaredField("SERVICES");
         field.setAccessible(true);
@@ -278,8 +275,7 @@ public class StoreTest {
 
         // Then
         verify(addElementsHandler).doOperation(addElements, context, store);
-        verify(mockICacheService, Mockito.atLeast(1)).getCache(any());
-        verify(mockICache, Mockito.atLeast(1)).put(any(), any());
+        verifyNoInteractions(mockICacheService, mockICache);
     }
 
     @Test
diff --git a/rest-api/core-rest/src/test/java/uk/gov/gchq/gaffer/rest/service/v2/JobServiceV2IT.java b/rest-api/core-rest/src/test/java/uk/gov/gchq/gaffer/rest/service/v2/JobServiceV2IT.java
index 75726cee28..71db1530f4 100644
--- a/rest-api/core-rest/src/test/java/uk/gov/gchq/gaffer/rest/service/v2/JobServiceV2IT.java
+++ b/rest-api/core-rest/src/test/java/uk/gov/gchq/gaffer/rest/service/v2/JobServiceV2IT.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019-2020 Crown Copyright
+ * Copyright 2019-2025 Crown Copyright
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -35,14 +35,12 @@ import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.assertj.core.api.Assertions.assertThat;
 
-public class JobServiceV2IT extends AbstractRestApiV2IT {
+class JobServiceV2IT extends AbstractRestApiV2IT {
 
     @Test
-    public void shouldCorrectlyDoAndThenCancelScheduledJob() throws IOException, InterruptedException {
+    void shouldCorrectlyDoAndThenCancelScheduledJob() throws IOException, InterruptedException {
         // When
         final Repeat repeat = new Repeat(1, 2, TimeUnit.SECONDS);
         Job job = new Job(repeat, new OperationChain.Builder().first(new GetAllElements()).build());
@@ -52,7 +50,7 @@ public class JobServiceV2IT extends AbstractRestApiV2IT {
         });
 
         // Then
-        assertEquals(201, jobSchedulingResponse.getStatus());
+        assertThat(jobSchedulingResponse.getStatus()).isEqualTo(201);
         String parentJobId = jobSchedulingDetail.getJobId();
 
         // Wait for first scheduled to run
@@ -65,10 +63,10 @@ public class JobServiceV2IT extends AbstractRestApiV2IT {
 
         for (JobDetail jobDetail : jobDetails) {
             if (null != jobDetail.getParentJobId() && jobDetail.getParentJobId().equals(parentJobId)) {
-                assertEquals(JobStatus.FINISHED, jobDetail.getStatus());
+                assertThat(jobDetail.getStatus()).isEqualTo(JobStatus.FINISHED);
             }
             if (jobDetail.getJobId().equals(parentJobId)) {
-                assertEquals(JobStatus.SCHEDULED_PARENT, jobDetail.getStatus());
+                assertThat(jobDetail.getStatus()).isEqualTo(JobStatus.SCHEDULED_PARENT);
             }
         }
 
@@ -81,13 +79,13 @@ public class JobServiceV2IT extends AbstractRestApiV2IT {
 
         for (JobDetail jobDetail : jobDetailsAfterCancelled) {
             if (parentJobId.equals(jobDetail.getJobId())) {
-                assertEquals(JobStatus.CANCELLED, jobDetail.getStatus());
+                assertThat(jobDetail.getStatus()).isEqualTo(JobStatus.CANCELLED);
             }
         }
     }
 
     @Test
-    public void shouldNotKeepScheduledJobsRunningAfterRestartWhenUsingInMemoryCache() throws IOException {
+    void shouldNotKeepScheduledJobsRunningAfterRestartWhenUsingInMemoryCache() throws IOException {
         // Given - schedule Job
         final Repeat repeat = new Repeat(1, 2, TimeUnit.SECONDS);
         Job job = new Job(repeat, new OperationChain.Builder().first(new GetAllElements()).build());
@@ -104,8 +102,8 @@ public class JobServiceV2IT extends AbstractRestApiV2IT {
         });
 
         // then - assert parent is of Scheduled parent
-        assertEquals(JobStatus.SCHEDULED_PARENT,
-                allJobDetails.stream().filter(jobDetail -> jobDetail.getJobId().equals(parentJobId)).findFirst().get().getStatus());
+        JobStatus jobStatus = allJobDetails.stream().filter(jobDetail -> jobDetail.getJobId().equals(parentJobId)).findFirst().get().getStatus();
+        assertThat(jobStatus).isEqualTo(JobStatus.SCHEDULED_PARENT);
 
         // Restart server to check Job still scheduled
         client.stopServer();
@@ -119,15 +117,15 @@ public class JobServiceV2IT extends AbstractRestApiV2IT {
                 });
 
         // Then - assert parent job id is not present
-        assertTrue(allJobDetails2.stream().noneMatch(jobDetail -> jobDetail.getJobId().equals(parentJobId)));
+        assertThat(allJobDetails2.stream().noneMatch(jobDetail -> jobDetail.getJobId().equals(parentJobId))).isTrue();
     }
 
     @Test
-    public void shouldReturnJobIdHeader() throws IOException {
+    void shouldNotReturnJobIdHeader() throws IOException {
         // When
         final Response response = client.executeOperation(new GetAllElements());
 
         // Then
-        assertNotNull(response.getHeaderString(ServiceConstants.JOB_ID_HEADER));
+        assertThat(response.getHeaders().toString()).doesNotContain(ServiceConstants.JOB_ID_HEADER);
     }
 }
diff --git a/rest-api/core-rest/src/test/java/uk/gov/gchq/gaffer/rest/service/v2/OperationServiceV2IT.java b/rest-api/core-rest/src/test/java/uk/gov/gchq/gaffer/rest/service/v2/OperationServiceV2IT.java
index ea5f944f05..f41ac2cd8d 100644
--- a/rest-api/core-rest/src/test/java/uk/gov/gchq/gaffer/rest/service/v2/OperationServiceV2IT.java
+++ b/rest-api/core-rest/src/test/java/uk/gov/gchq/gaffer/rest/service/v2/OperationServiceV2IT.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015-2021 Crown Copyright
+ * Copyright 2015-2025 Crown Copyright
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -51,25 +51,21 @@ import java.util.Set;
 import java.util.stream.Collectors;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 import static uk.gov.gchq.gaffer.core.exception.Status.SERVICE_UNAVAILABLE;
 
 public class OperationServiceV2IT extends OperationServiceIT {
 
     @Test
-    public void shouldReturnJobIdHeader() throws IOException {
+    void shouldNotReturnJobIdHeader() throws IOException {
         // When
         final Response response = client.executeOperation(new GetAllElements());
 
         // Then
-        assertNotNull(response.getHeaderString(ServiceConstants.JOB_ID_HEADER));
+        assertThat(response.getHeaderString(ServiceConstants.JOB_ID_HEADER)).isNull();
     }
 
     @Test
-    public void shouldReturn403WhenUnauthorised() throws IOException {
+    void shouldReturn403WhenUnauthorised() throws IOException {
         // Given
         final Graph graph = new Graph.Builder()
                 .config(StreamUtil.graphConfig(this.getClass()))
@@ -82,11 +78,11 @@ public class OperationServiceV2IT extends OperationServiceIT {
         final Response response = client.executeOperation(new GetAllElements());
 
         // Then
-        assertEquals(403, response.getStatus());
+        assertThat(response.getStatus()).isEqualTo(403);
     }
 
     @Test
-    public void shouldPropagateStatusInformationContainedInOperationExceptionsThrownByOperationHandlers()
+    void shouldPropagateStatusInformationContainedInOperationExceptionsThrownByOperationHandlers()
             throws IOException {
         // Given
         final StoreProperties storeProperties = StoreProperties.loadStoreProperties(StreamUtil.STORE_PROPERTIES);
@@ -103,11 +99,11 @@ public class OperationServiceV2IT extends OperationServiceIT {
         final Response response = client.executeOperation(new GetAllJobDetails());
 
         // Then
-        assertEquals(SERVICE_UNAVAILABLE.getStatusCode(), response.getStatus());
+        assertThat(response.getStatus()).isEqualTo(SERVICE_UNAVAILABLE.getStatusCode());
     }
 
     @Test
-    public void shouldReturnSameJobIdInHeaderAsGetAllJobDetailsOperation() throws IOException {
+    void shouldNotHaveJobIdInHeader() throws IOException {
         // Given
         final Graph graph = new Graph.Builder()
                 .config(StreamUtil.graphConfig(this.getClass()))
@@ -121,11 +117,12 @@ public class OperationServiceV2IT extends OperationServiceIT {
         final Response response = client.executeOperation(new GetAllJobDetails());
 
         // Then
-        assertTrue(response.readEntity(String.class).contains(response.getHeaderString("job-id")));
+        assertThat(response.getHeaders()).isNotEmpty();
+        assertThat(response.readEntity(String.class)).doesNotContain("job-id");
     }
 
     @Test
-    public void shouldReturnAllOperationsAsOperationDetails() throws IOException, ClassNotFoundException {
+    void shouldReturnAllOperationsAsOperationDetails() throws IOException, ClassNotFoundException {
         // Given
         final Set<Class<? extends Operation>> expectedOperations = client.getDefaultGraphFactory().getGraph()
                 .getSupportedOperations();
@@ -146,7 +143,7 @@ public class OperationServiceV2IT extends OperationServiceIT {
     }
 
     @Test
-    public void shouldReturnOperationDetailSummaryOfClass() throws Exception {
+    void shouldReturnOperationDetailSummaryOfClass() throws Exception {
         // Given
         final String expectedSummary = "\"summary\":\"Gets elements related to provided seeds\"";
 
@@ -154,11 +151,11 @@ public class OperationServiceV2IT extends OperationServiceIT {
         final Response response = client.getOperationDetails(GetElements.class);
 
         // Then
-        assertTrue(response.readEntity(String.class).contains(expectedSummary));
+        assertThat(response.readEntity(String.class)).contains(expectedSummary);
     }
 
     @Test
-    public void shouldReturnOutputClassForOperationWithOutput() throws Exception {
+    void shouldReturnOutputClassForOperationWithOutput() throws Exception {
         // Given
         final String expectedOutputString = "\"outputClassName\":\"java.lang.Iterable<uk.gov.gchq.gaffer.data.element.Element>\"";
 
@@ -166,11 +163,11 @@ public class OperationServiceV2IT extends OperationServiceIT {
         final Response response = client.getOperationDetails(GetElements.class);
 
         // Then
-        assertTrue(response.readEntity(String.class).contains(expectedOutputString));
+        assertThat(response.readEntity(String.class)).contains(expectedOutputString);
     }
 
     @Test
-    public void shouldNotIncludeAnyOutputClassForOperationWithoutOutput() throws Exception {
+    void shouldNotIncludeAnyOutputClassForOperationWithoutOutput() throws Exception {
         // Given
         final String outputClassNameString = "\"outputClassName\"";
 
@@ -178,11 +175,11 @@ public class OperationServiceV2IT extends OperationServiceIT {
         final Response response = client.getOperationDetails(DiscardOutput.class);
 
         // Then
-        assertFalse(response.readEntity(String.class).contains(outputClassNameString));
+        assertThat(response.readEntity(String.class)).doesNotContain(outputClassNameString);
     }
 
     @Test
-    public void shouldReturnOptionsAndSummariesForEnumFields() throws Exception {
+    void shouldReturnOptionsAndSummariesForEnumFields() throws Exception {
         // When
         final Response response = client.getOperationDetails(GetElements.class);
 
@@ -197,11 +194,11 @@ public class OperationServiceV2IT extends OperationServiceIT {
                 new OperationFieldPojo("directedType", "java.lang.String", false, "Is the Edge directed?", Sets.newHashSet("DIRECTED", "UNDIRECTED", "EITHER")),
                 new OperationFieldPojo("views", "java.util.List<uk.gov.gchq.gaffer.data.elementdefinition.view.View>", false, null, null)
         );
-        assertEquals(fields, opDetails.getFields());
+        assertThat(opDetails.getFields()).isEqualTo(fields);
     }
 
     @Test
-    public void shouldAllowUserWithAuthThroughHeaders() throws IOException {
+    void shouldAllowUserWithAuthThroughHeaders() throws IOException {
         // Given
         System.setProperty(SystemProperty.USER_FACTORY_CLASS, TestUserFactory.class.getName());
         client.stopServer();
@@ -221,11 +218,11 @@ public class OperationServiceV2IT extends OperationServiceIT {
         final Response response = ((RestApiV2TestClient) client).executeOperationChainChunkedWithHeaders(opChain, "ListUser");
 
         // Then
-        assertEquals(200, response.getStatus());
+        assertThat(response.getStatus()).isEqualTo(200);
     }
 
     @Test
-    public void shouldNotAllowUserWithNoAuthThroughHeaders() throws IOException {
+    void shouldNotAllowUserWithNoAuthThroughHeaders() throws IOException {
         // Given
         System.setProperty(SystemProperty.USER_FACTORY_CLASS, TestUserFactory.class.getName());
         client.stopServer();
@@ -246,7 +243,7 @@ public class OperationServiceV2IT extends OperationServiceIT {
                 "BasicUser");
 
         // Then
-        assertEquals(500, response.getStatus());
+        assertThat(response.getStatus()).isEqualTo(500);
     }
 
     @Override
diff --git a/rest-api/spring-rest/src/test/java/uk/gov/gchq/gaffer/rest/integration/controller/OperationControllerIT.java b/rest-api/spring-rest/src/test/java/uk/gov/gchq/gaffer/rest/integration/controller/OperationControllerIT.java
index dddbdb2435..6ebb42032a 100644
--- a/rest-api/spring-rest/src/test/java/uk/gov/gchq/gaffer/rest/integration/controller/OperationControllerIT.java
+++ b/rest-api/spring-rest/src/test/java/uk/gov/gchq/gaffer/rest/integration/controller/OperationControllerIT.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020-2024 Crown Copyright
+ * Copyright 2020-2025 Crown Copyright
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -45,21 +45,17 @@ import uk.gov.gchq.gaffer.store.schema.TypeDefinition;
 import uk.gov.gchq.koryphe.impl.binaryoperator.StringConcat;
 import uk.gov.gchq.koryphe.util.ReflectionUtil;
 
-import java.io.IOException;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.when;
 import static uk.gov.gchq.gaffer.core.exception.Status.SERVICE_UNAVAILABLE;
 import static uk.gov.gchq.gaffer.jsonserialisation.JSONSerialiser.createDefaultMapper;
 
-public class OperationControllerIT extends AbstractRestApiIT {
+class OperationControllerIT extends AbstractRestApiIT {
 
     @Autowired
     private GraphFactory graphFactory; // This will be a Mock (see application-test.properties)
@@ -70,7 +66,7 @@ public class OperationControllerIT extends AbstractRestApiIT {
 
 
     @Test
-    public void shouldReturnHelpfulErrorMessageIfJsonIsIncorrect() {
+    void shouldReturnHelpfulErrorMessageIfJsonIsIncorrect() {
         // Given
         Graph graph = new Graph.Builder()
                 .config(StreamUtil.graphConfig(this.getClass()))
@@ -83,7 +79,7 @@ public class OperationControllerIT extends AbstractRestApiIT {
         // When
         String request = "{\"class\"\"GetAllElements\"}";
 
-        LinkedMultiValueMap headers = new LinkedMultiValueMap();
+        LinkedMultiValueMap<String, String> headers = new LinkedMultiValueMap<String, String>();
         headers.add("Content-Type", "application/json;charset=utf-8");
 
         final ResponseEntity<Error> response = post("/graph/operations/execute",
@@ -91,13 +87,13 @@ public class OperationControllerIT extends AbstractRestApiIT {
                 Error.class);
 
         // Then
-        assertEquals(400, response.getStatusCode().value());
-        assertEquals(400, response.getBody().getStatusCode());
-        assertTrue(response.getBody().getSimpleMessage().contains("was expecting a colon to separate field name and value"));
+        assertThat(response.getStatusCode().value()).isEqualTo(400);
+        assertThat(response.getBody().getStatusCode()).isEqualTo(400);
+        assertThat(response.getBody().getSimpleMessage()).contains("was expecting a colon to separate field name and value");
     }
 
     @Test
-    public void shouldReturnHelpfulErrorMessageIfOperationIsUnsupported() {
+    void shouldReturnHelpfulErrorMessageIfOperationIsUnsupported() {
         // Given
         Graph graph = new Graph.Builder()
             .config(StreamUtil.graphConfig(this.getClass()))
@@ -113,13 +109,12 @@ public class OperationControllerIT extends AbstractRestApiIT {
             Error.class);
 
         // Then
-
-        assertNotNull(response.getBody().getSimpleMessage());
-        assertTrue(response.getBody().getSimpleMessage().contains("GetAllGraphIds is not supported by the MapStore"));
+        assertThat(response.getBody().getSimpleMessage()).isNotNull();
+        assertThat(response.getBody().getSimpleMessage()).contains("GetAllGraphIds is not supported by the MapStore");
     }
 
     @Test
-    public void shouldReturn403WhenUnauthorised() throws IOException {
+    void shouldReturn403WhenUnauthorised() {
         // Given
         Graph graph = new Graph.Builder()
                 .config(StreamUtil.graphConfig(this.getClass()))
@@ -135,12 +130,12 @@ public class OperationControllerIT extends AbstractRestApiIT {
                 Error.class);
 
         // Then
-        assertEquals(403, response.getStatusCode().value());
-        assertEquals(403, response.getBody().getStatusCode());
+        assertThat(response.getStatusCode().value()).isEqualTo(403);
+        assertThat(response.getBody().getStatusCode()).isEqualTo(403);
     }
 
     @Test
-    public void shouldPropagateStatusInformationContainedInOperationExceptionsThrownByOperationHandlers() throws IOException {
+    void shouldPropagateStatusInformationContainedInOperationExceptionsThrownByOperationHandlers() {
         // Given
         final StoreProperties storeProperties = new MapStoreProperties();
         storeProperties.set(StoreProperties.JOB_TRACKER_ENABLED, Boolean.FALSE.toString());
@@ -158,11 +153,11 @@ public class OperationControllerIT extends AbstractRestApiIT {
                 new GetAllJobDetails(),
                 Error.class);
         // Then
-        assertEquals(SERVICE_UNAVAILABLE.getStatusCode(), response.getStatusCode().value());
+        assertThat(response.getStatusCode().value()).isEqualTo(SERVICE_UNAVAILABLE.getStatusCode());
     }
 
     @Test
-    public void shouldReturnSameJobIdInHeaderAsGetAllJobDetailsOperation() throws IOException {
+    void shouldNotReturnJobIdInHeader() {
         // Given
         StoreProperties properties = new MapStoreProperties();
         properties.setJobTrackerEnabled(true);
@@ -182,18 +177,11 @@ public class OperationControllerIT extends AbstractRestApiIT {
                 Set.class);
 
         // Then
-        try {
-            assertTrue(response.getBody().toString().contains(response.getHeaders().get("job-id").get(0)));
-        } catch (final AssertionError e) {
-            System.out.println("Job ID was not found in the Header");
-            System.out.println("Header was: " + response.getHeaders().get("job-id"));
-            System.out.println("Body was: " + response.getBody());
-            throw e;
-        }
+        assertThat(response.getHeaders().toString()).doesNotContain("job-id");
     }
 
     @Test
-    public void shouldCorrectlyStreamExecuteChunked() throws Exception {
+    void shouldCorrectlyStreamExecuteChunked() throws Exception {
         // Given
         final Schema schema =  new Schema.Builder()
                 .entity("g1", new SchemaEntityDefinition.Builder()
@@ -241,11 +229,11 @@ public class OperationControllerIT extends AbstractRestApiIT {
 
         // Then
         String expected = mapper.writeValueAsString(ent1) + "\r\n" + mapper.writeValueAsString(ent2) + "\r\n";
-        assertEquals(expected, response.getBody());
+        assertThat(response.getBody()).isEqualTo(expected);
     }
 
     @Test
-    public void shouldCorrectlySerialiseAllOperationDetails() throws IOException {
+    void shouldCorrectlySerialiseAllOperationDetails() {
         // Given
         final Graph graph = new Graph.Builder()
                 .config(StreamUtil.graphConfig(this.getClass()))