Repository: ministryofjustice/laa-crime-means-assessment
Pull request #482: LCAM-1805-web Client Customizer changes
Author: login: loganathanMOJ
Created: 2025-04-15T13:40:21Z | Merged: 2025-04-29T14:29:21Z

## What

[Link to story](https://dsdmoj.atlassian.net/browse/LCAM-1805)

New web client implementation  with resilience4j  for better error handling 

## Checklist

Before you ask people to review this PR:

- [ ] Tests should be passing: `./gradlew test`
- [ ] Github should not be reporting conflicts; you should have recently run `git rebase main`.
- [ ] Avoid mixing whitespace changes with code changes in the same commit. These make diffs harder to read and conflicts more likely.
- [ ] You should have looked at the diff against main and ensured that nothing unexpected is included in your changes.
- [ ] You should have checked that the commit messages say why the change was made.

## Additional checks

- Don’t forget to [run](https://github.com/ministryofjustice/laa-crimeapps-maat-functional-tests/actions/workflows/ExecuteUiTests.yaml) the MAAT functional test suite after deploying your changes to the DEV or TEST environments to ensure your changes haven’t broken any of the functional tests.


Diff:
diff --git a/crime-means-assessment/build.gradle b/crime-means-assessment/build.gradle
index ebc2b2cb..e8e36783 100644
--- a/crime-means-assessment/build.gradle
+++ b/crime-means-assessment/build.gradle
@@ -13,7 +13,6 @@ def versions = [
         springdocVersion             : "2.1.0",
         liquibase                    : "4.29.1",
         crimeCommonsClassesVersion   : "3.18.0",
-        crimeCommonsRestClientVersion: "3.20.0",
         crimeCommonsModsSchemas      : "1.36.0",
         mockitoInlineVersion         : "5.2.0",
         cucumberVersion              : "7.21.0",
@@ -21,7 +20,10 @@ def versions = [
         springCloudStubRunnerVersion : "4.0.4",
         h2DBVersion                  : '2.1.210',
         postgresqlVersion            : "42.7.2",
-        tomcatVersion                : "10.1.34",
+        tomcatVersion                : "10.1.39",
+        oauth2ResourceServer         : "3.4.1",
+        securityCrypto               : "6.4.4",
+        resilience4jVersion          : "2.2.0",
         oauth2ResourceServer         : "3.4.1",
         securityCrypto               : "6.4.4"
 ]
@@ -43,7 +45,7 @@ repositories {
 }
 
 dependencies {
-    implementation "uk.gov.justice.service.laa-crime:crime-commons-spring-boot-starter-rest-client:$versions.crimeCommonsRestClientVersion:all"
+    implementation("org.jetbrains:annotations:24.1.0")
     implementation "uk.gov.justice.service.laa-crime:crime-commons-classes:$versions.crimeCommonsClassesVersion"
     implementation "uk.gov.justice.service.laa-crime:crime-commons-mod-schemas:$versions.crimeCommonsModsSchemas"
     
@@ -51,13 +53,22 @@ dependencies {
     implementation "org.springframework.boot:spring-boot-starter-validation"
     implementation "org.springframework.boot:spring-boot-starter-oauth2-resource-server:$versions.oauth2ResourceServer"
     implementation "org.springframework.security:spring-security-crypto:$versions.securityCrypto"
+    implementation "org.springframework.boot:spring-boot-starter-actuator"
+    implementation "org.springframework.boot:spring-boot-starter-webflux"
+    implementation "org.springframework.boot:spring-boot-starter-web"
+    implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
+    implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:$versions.springdocVersion"
 
     implementation "org.apache.tomcat.embed:tomcat-embed-core:$versions.tomcatVersion"
 
     implementation "org.postgresql:postgresql:$versions.postgresqlVersion"
-    implementation "io.micrometer:micrometer-registry-prometheus"
     implementation "org.liquibase:liquibase-core:$versions.liquibase"
-    implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:$versions.springdocVersion"
+
+    implementation "io.micrometer:micrometer-registry-prometheus"
+    implementation "io.micrometer:micrometer-tracing"
+
+    implementation "io.github.resilience4j:resilience4j-reactor:$versions.resilience4jVersion"
+    implementation "io.github.resilience4j:resilience4j-spring-boot3:$versions.resilience4jVersion"
 
     compileOnly "org.projectlombok:lombok"
     annotationProcessor "org.projectlombok:lombok"
@@ -74,6 +85,8 @@ dependencies {
     testImplementation "io.cucumber:cucumber-spring"
     testImplementation "io.cucumber:cucumber-junit-platform-engine"
     testImplementation "org.junit.platform:junit-platform-suite:$versions.jUnitPlatformSuiteVersion"
+    testCompileOnly "org.projectlombok:lombok"
+    testAnnotationProcessor "org.projectlombok:lombok"
 }
 
 configurations {
diff --git a/crime-means-assessment/docker-compose.local.yml b/crime-means-assessment/docker-compose.local.yml
index e3097bff..30362918 100644
--- a/crime-means-assessment/docker-compose.local.yml
+++ b/crime-means-assessment/docker-compose.local.yml
@@ -3,7 +3,7 @@ version: "3.2"
 services:
   app:
     environment:
-      - MAAT_API_BASE_URL=http://court-data-api:8090/
+      - MAAT_API_BASE_URL=http://court-data-api:8090/api/internal/v1/assessment
 
     networks:
       default:
@@ -11,4 +11,4 @@ services:
 
 networks:
   local-container-network:
-    external: true
\ No newline at end of file
+    external: true
diff --git a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/client/MaatCourtDataApiClient.java b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/client/MaatCourtDataApiClient.java
new file mode 100644
index 00000000..8802416f
--- /dev/null
+++ b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/client/MaatCourtDataApiClient.java
@@ -0,0 +1,58 @@
+package uk.gov.justice.laa.crime.meansassessment.client;
+
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.service.annotation.*;
+import uk.gov.justice.laa.crime.common.model.meansassessment.maatapi.MaatApiAssessmentRequest;
+import uk.gov.justice.laa.crime.common.model.meansassessment.maatapi.MaatApiAssessmentResponse;
+import uk.gov.justice.laa.crime.meansassessment.dto.AuthorizationResponseDTO;
+import uk.gov.justice.laa.crime.meansassessment.dto.OutstandingAssessmentResultDTO;
+import uk.gov.justice.laa.crime.meansassessment.dto.maatcourtdata.*;
+
+import java.util.Map;
+
+@HttpExchange()
+public interface MaatCourtDataApiClient {
+
+    @PostExchange("/financial-assessments")
+    MaatApiAssessmentResponse createFinancialAssessment(@RequestBody MaatApiAssessmentRequest assessment);
+
+    @PutExchange("/financial-assessments")
+    MaatApiAssessmentResponse updateFinancialAssessment(@RequestBody MaatApiAssessmentRequest assessment);
+
+    @GetExchange("/financial-assessments/{financialAssessmentId}")
+    FinancialAssessmentDTO getFinancialAssessment(@PathVariable Integer financialAssessmentId);
+
+    @PatchExchange("/financial-assessments/rollback/{financialAssessmentId}")
+    void patchFinancialAssessment(@RequestBody Map<String, Object> updateFields,
+                                  @PathVariable Integer financialAssessmentId);
+
+    @PostExchange("/rep-orders/update-date-completed")
+    RepOrderDTO updateCompletionDate(@RequestBody DateCompletionRequestDTO dateCompletionRequestDTO);
+
+    @GetExchange("/passport-assessments/repId/{repId}")
+    PassportAssessmentDTO getPassportAssessmentFromRepId(@PathVariable Integer repId);
+
+    @GetExchange("/hardship/repId/{repId}")
+    HardshipReviewDTO getHardshipReviewFromRepId(@PathVariable Integer repId);
+
+    @GetExchange("/ioj-appeal/repId/{repId}")
+    IOJAppealDTO getIOJAppealFromRepId(@PathVariable Integer repId);
+
+    @GetExchange("/rep-orders/{repId}")
+    RepOrderDTO getRepOrder(@PathVariable Integer repId);
+
+    @GetExchange("/authorization/users/{username}/actions/{action}")
+    AuthorizationResponseDTO getUserRoleAction(@PathVariable String username, @PathVariable String action);
+
+    @GetExchange("/authorization/users/{username}/work-reasons/{nworCode}")
+    AuthorizationResponseDTO getNewWorkReason(@PathVariable String username, @PathVariable String nworCode);
+
+    @GetExchange("/financial-assessments/check-outstanding/{repId}")
+    OutstandingAssessmentResultDTO getOutstandingAssessment(@PathVariable Integer repId);
+
+    @GetExchange("/authorization/users/{username}/reservations/{reservationId}/sessions/{sessionId}")
+    AuthorizationResponseDTO getReservationDetail(@PathVariable String username, @PathVariable Integer reservationId,
+                                                  @PathVariable String sessionId);
+
+}
diff --git a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/config/MaatApiConfiguration.java b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/config/MaatApiConfiguration.java
deleted file mode 100644
index 28a56071..00000000
--- a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/config/MaatApiConfiguration.java
+++ /dev/null
@@ -1,160 +0,0 @@
-package uk.gov.justice.laa.crime.meansassessment.config;
-
-import lombok.*;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-import jakarta.validation.constraints.NotNull;
-import uk.gov.justice.laa.crime.enums.RequestType;
-
-@Data
-@ConfigurationProperties(prefix = "maat-api")
-public class MaatApiConfiguration {
-    /**
-     * The API's Base URL
-     */
-    @NotNull
-    private String baseUrl;
-
-    /**
-     * Determines whether oAuth authentication is enabled
-     */
-    @NotNull
-    private boolean oAuthEnabled;
-
-    /**
-     * Defines the url for assessment post processing.
-     */
-    @NotNull
-    private String postProcessingUrl;
-
-    /**
-     * Defines validation endpoint URLs
-     */
-    @NotNull
-    private ValidationEndpoints validationEndpoints;
-
-    /**
-     * Defines financial assessment endpoint URLs
-     */
-    @NotNull
-    private FinancialAssessmentEndpoints financialAssessmentEndpoints;
-
-    @NotNull
-    private RepOrderEndpoints repOrderEndpoints;
-
-    @NotNull
-    private PassportAssessmentEndpoints passportAssessmentEndpoints;
-
-    @NotNull
-    private HardshipReviewEndpoints hardshipReviewEndpoints;
-
-    @NotNull
-    private IOJAppealEndpoints iojAppealEndpoints;
-
-    @Getter
-    @AllArgsConstructor
-    @Setter
-    @NoArgsConstructor
-    public static class ValidationEndpoints {
-        /**
-         * Validate Role Action Endpoint URL
-         */
-        @NotNull
-        private String roleActionUrl;
-
-        /**
-         * Validate New Work Reason Endpoint URL
-         */
-        @NotNull
-        private String newWorkReasonUrl;
-
-        /**
-         * Validate Reservation Endpoint URL
-         */
-        @NotNull
-        private String reservationsUrl;
-
-        /**
-         * Check Outstanding Assessments Endpoint URL
-         */
-        @NotNull
-        private String outstandingAssessmentsUrl;
-    }
-
-    @Getter
-    @AllArgsConstructor
-    @Setter
-    @NoArgsConstructor
-    public static class FinancialAssessmentEndpoints {
-        /**
-         * Find assessment URL
-         */
-        @NotNull
-        private String searchUrl;
-
-        /**
-         * Create assessment URL
-         */
-        @NotNull
-        private String createUrl;
-
-        /**
-         * Update assessment URL
-         */
-        @NotNull
-        private String updateUrl;
-
-        /**
-         * Rollback assessment URL
-         */
-        @NotNull
-        private String rollbackUrl;
-
-        public String getByRequestType(RequestType requestType) {
-            return (requestType.equals(RequestType.CREATE)) ? createUrl : updateUrl;
-        }
-    }
-
-    @Getter
-    @Setter
-    @AllArgsConstructor
-    @NoArgsConstructor
-    public static class RepOrderEndpoints {
-
-        @NotNull
-        private String findUrl;
-
-        @NotNull
-        private String dateCompletionUrl;
-    }
-
-    @Getter
-    @Setter
-    @AllArgsConstructor
-    @NoArgsConstructor
-    public static class PassportAssessmentEndpoints {
-
-        @NotNull
-        private String findUrl;
-    }
-
-    @Getter
-    @Setter
-    @AllArgsConstructor
-    @NoArgsConstructor
-    public static class HardshipReviewEndpoints {
-
-        @NotNull
-        private String findUrl;
-    }
-
-    @Getter
-    @Setter
-    @AllArgsConstructor
-    @NoArgsConstructor
-    public static class IOJAppealEndpoints {
-
-        @NotNull
-        private String findUrl;
-    }
-}
diff --git a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/config/ServicesConfiguration.java b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/config/ServicesConfiguration.java
new file mode 100644
index 00000000..b8e93b47
--- /dev/null
+++ b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/config/ServicesConfiguration.java
@@ -0,0 +1,31 @@
+package uk.gov.justice.laa.crime.meansassessment.config;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "services")
+public class ServicesConfiguration {
+
+    @NotNull
+    private MaatApi maatApi;
+
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class MaatApi {
+        /**
+         * The API's Base URL
+         */
+        @NotNull
+        private String baseUrl;
+
+        @NotNull
+        private String registrationId;
+
+    }
+
+}
diff --git a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/config/WebClientsConfiguration.java b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/config/WebClientsConfiguration.java
new file mode 100644
index 00000000..08d14af2
--- /dev/null
+++ b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/config/WebClientsConfiguration.java
@@ -0,0 +1,110 @@
+package uk.gov.justice.laa.crime.meansassessment.config;
+
+import io.github.resilience4j.retry.RetryRegistry;
+import io.netty.resolver.DefaultAddressResolverGroup;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
+import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
+import org.springframework.util.Assert;
+import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.support.WebClientAdapter;
+import org.springframework.web.service.invoker.HttpServiceProxyFactory;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.resources.ConnectionProvider;
+import uk.gov.justice.laa.crime.meansassessment.client.MaatCourtDataApiClient;
+import uk.gov.justice.laa.crime.meansassessment.filter.Resilience4jRetryFilter;
+import uk.gov.justice.laa.crime.meansassessment.filter.WebClientFilters;
+
+import java.time.Duration;
+import java.util.List;
+
+@Slf4j
+@Configuration
+@AllArgsConstructor
+public class WebClientsConfiguration {
+    public static final int MAX_IN_MEMORY_SIZE = 10485760;
+    public static final String COURT_DATA_API_WEB_CLIENT_NAME = "maatCourtDataWebClient";
+
+    @Bean
+    WebClientCustomizer webClientCustomizer() {
+        ConnectionProvider provider =
+                ConnectionProvider.builder("custom")
+                        .maxConnections(500)
+                        .maxIdleTime(Duration.ofSeconds(20))
+                        .maxLifeTime(Duration.ofSeconds(60))
+                        .evictInBackground(Duration.ofSeconds(120))
+                        .pendingAcquireTimeout(Duration.ofSeconds(60))
+                        .build();
+
+        return builder -> {
+            builder.clientConnector(
+                    new ReactorClientHttpConnector(
+                            HttpClient.create(provider)
+                                    .resolver(DefaultAddressResolverGroup.INSTANCE)
+                                    .compress(true)
+                                    .responseTimeout(Duration.ofSeconds(30))
+                    )
+            );
+            builder.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
+            builder.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
+            builder.codecs(configurer -> configurer
+                    .defaultCodecs()
+                    .maxInMemorySize(MAX_IN_MEMORY_SIZE)
+            );
+        };
+    }
+
+    @Bean(COURT_DATA_API_WEB_CLIENT_NAME)
+    WebClient maatCourtDataWebClient(WebClient.Builder webClientBuilder,
+                                     ServicesConfiguration servicesConfiguration,
+                                     ClientRegistrationRepository clientRegistrations,
+                                     OAuth2AuthorizedClientRepository authorizedClients,
+                                     RetryRegistry retryRegistry) {
+
+        ServletOAuth2AuthorizedClientExchangeFilterFunction oauthFilter =
+                new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
+        String registrationId = servicesConfiguration.getMaatApi().getRegistrationId();
+        Assert.notNull(registrationId, "CMA API registration id cannot be null");
+        oauthFilter.setDefaultClientRegistrationId(registrationId);
+
+        Resilience4jRetryFilter retryFilter =
+                new Resilience4jRetryFilter(retryRegistry, COURT_DATA_API_WEB_CLIENT_NAME);
+
+        return webClientBuilder
+                .baseUrl(servicesConfiguration.getMaatApi().getBaseUrl())
+                .filters(filters -> configureFilters(filters, oauthFilter, retryFilter))
+                .build();
+    }
+
+    @Bean
+    MaatCourtDataApiClient maatCourtDataApiClient(
+            @Qualifier(COURT_DATA_API_WEB_CLIENT_NAME) WebClient maatCourtDataWebClient) {
+        HttpServiceProxyFactory httpServiceProxyFactory =
+                HttpServiceProxyFactory
+                        .builderFor(WebClientAdapter.create(maatCourtDataWebClient))
+                        .build();
+        return httpServiceProxyFactory.createClient(MaatCourtDataApiClient.class);
+    }
+
+
+    private void configureFilters(List<ExchangeFilterFunction> filters,
+                                  ServletOAuth2AuthorizedClientExchangeFilterFunction oauthFilter,
+                                  ExchangeFilterFunction retryFilter) {
+        filters.add(WebClientFilters.logRequestHeaders());
+        filters.add(retryFilter);
+        filters.add(oauthFilter);
+        filters.add(WebClientFilters.errorResponseHandler());
+        filters.add(WebClientFilters.handleNotFoundResponse());
+        filters.add(WebClientFilters.logResponse());
+    }
+}
diff --git a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/exception/RestControllerAdviser.java b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/exception/RestControllerAdviser.java
index 1e69862c..f7e58d2d 100644
--- a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/exception/RestControllerAdviser.java
+++ b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/exception/RestControllerAdviser.java
@@ -1,16 +1,22 @@
 package uk.gov.justice.laa.crime.meansassessment.exception;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.HttpStatusCode;
 import org.springframework.http.ResponseEntity;
 import org.springframework.http.converter.HttpMessageNotReadableException;
 import org.springframework.web.bind.MethodArgumentNotValidException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseStatus;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
-import uk.gov.justice.laa.crime.commons.tracing.TraceIdHandler;
-import uk.gov.justice.laa.crime.meansassessment.dto.ErrorDTO;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+import uk.gov.justice.laa.crime.dto.ErrorDTO;
+import uk.gov.justice.laa.crime.meansassessment.tracing.TraceIdHandler;
+
+import java.io.IOException;
 
 @RestControllerAdvice
 @Slf4j
@@ -18,6 +24,25 @@
 public class RestControllerAdviser {
 
     private final TraceIdHandler traceIdHandler;
+    private final ObjectMapper mapper;
+
+    @ExceptionHandler(WebClientResponseException.class)
+    public ResponseEntity<ErrorDTO> onRuntimeException(WebClientResponseException exception) {
+        String errorMessage;
+        try {
+            ErrorDTO errorDTO = mapper.readValue(exception.getResponseBodyAsString(), ErrorDTO.class);
+            errorMessage = errorDTO.getMessage();
+        } catch (IOException ex) {
+            log.warn("Unable to read the ErrorDTO from WebClientResponseException", ex);
+            errorMessage = exception.getMessage();
+        }
+        return getNewErrorResponseWith(exception.getStatusCode(), errorMessage, traceIdHandler.getTraceId());
+    }
+
+    @ExceptionHandler(WebClientRequestException.class)
+    public ResponseEntity<ErrorDTO> onRuntimeException(WebClientRequestException exception) {
+        return getNewErrorResponseWith(HttpStatus.INTERNAL_SERVER_ERROR, exception.getMessage(), traceIdHandler.getTraceId());
+    }
 
     @ExceptionHandler(MethodArgumentNotValidException.class)
     @ResponseStatus(HttpStatus.BAD_REQUEST)
@@ -33,12 +58,6 @@ public ResponseEntity<ErrorDTO> adviceParseError(HttpMessageNotReadableException
         return getNewErrorResponseWith(HttpStatus.BAD_REQUEST, ex.getMessage(), traceIdHandler.getTraceId());
     }
 
-    @ExceptionHandler(APIClientException.class)
-    public ResponseEntity<ErrorDTO> handleApiClientError(APIClientException ex) {
-        log.error("APIClientException: ", ex);
-        return getNewErrorResponseWith(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage(), traceIdHandler.getTraceId());
-    }
-
     @ExceptionHandler(ValidationException.class)
     public ResponseEntity<ErrorDTO> handleValidationError(ValidationException ex) {
         log.warn("ValidationException: ", ex);
@@ -46,13 +65,13 @@ public ResponseEntity<ErrorDTO> handleValidationError(ValidationException ex) {
     }
 
     @ExceptionHandler(RuntimeException.class)
-    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
     public ResponseEntity<ErrorDTO> adviceServiceErrors(RuntimeException ex) {
         log.error("Service is failed due to in internal error.", ex);
         return getNewErrorResponseWith(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage(), traceIdHandler.getTraceId());
     }
 
-    private static ResponseEntity<ErrorDTO> getNewErrorResponseWith(HttpStatus status, String errorMessage, String traceId) {
+    private static ResponseEntity<ErrorDTO> getNewErrorResponseWith(HttpStatusCode status, String errorMessage, String traceId) {
         return new ResponseEntity<>(ErrorDTO.builder().traceId(traceId).code(status.toString()).message(errorMessage).build(), status);
     }
+
 }
diff --git a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/filter/Resilience4jRetryFilter.java b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/filter/Resilience4jRetryFilter.java
new file mode 100644
index 00000000..0c51811b
--- /dev/null
+++ b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/filter/Resilience4jRetryFilter.java
@@ -0,0 +1,55 @@
+package uk.gov.justice.laa.crime.meansassessment.filter;
+
+import io.github.resilience4j.reactor.retry.RetryOperator;
+import io.github.resilience4j.retry.Retry;
+import io.github.resilience4j.retry.RetryRegistry;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.lang.NonNull;
+import org.springframework.web.reactive.function.client.ClientRequest;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.ExchangeFunction;
+import reactor.core.publisher.Mono;
+
+import java.util.Set;
+
+@Slf4j
+public class Resilience4jRetryFilter implements ExchangeFilterFunction {
+
+    private final Retry retry;
+    private static final String DEFAULT_RETRY = "default";
+
+    public Resilience4jRetryFilter(RetryRegistry retryRegistry, String clientName) {
+        Set<String> availableRetries = retryRegistry.getAllRetries()
+                .stream()
+                .map(Retry::getName)
+                .collect(java.util.stream.Collectors.toSet());
+
+        String retryName = availableRetries.contains(clientName) ? clientName : DEFAULT_RETRY;
+        retry = retryRegistry.retry(retryName);
+
+        retry.getEventPublisher()
+                .onSuccess(event ->
+                                   log.info("✅ Request succeeded after {} attempts",
+                                            event.getNumberOfRetryAttempts()
+                                   )
+                )
+                .onRetry(event ->
+                                 log.info("🔄 Retry #{} after {}ms for request",
+                                          event.getNumberOfRetryAttempts(), event.getWaitInterval().toMillis()
+                                 )
+                )
+                .onError(event ->
+                                 log.error(
+                                         "🚨 Request failed after {} retry attempts. Giving up.",
+                                         event.getNumberOfRetryAttempts()
+                                 )
+                );
+    }
+
+    @Override
+    public @NonNull Mono<ClientResponse> filter(@NonNull ClientRequest request, @NonNull ExchangeFunction next) {
+        return next.exchange(request)
+                .transformDeferred(RetryOperator.of(retry));
+    }
+}
\ No newline at end of file
diff --git a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/filter/WebClientFilters.java b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/filter/WebClientFilters.java
new file mode 100644
index 00000000..ebe56566
--- /dev/null
+++ b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/filter/WebClientFilters.java
@@ -0,0 +1,58 @@
+package uk.gov.justice.laa.crime.meansassessment.filter;
+
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
+import reactor.core.publisher.Mono;
+
+@Slf4j
+@UtilityClass
+public class WebClientFilters {
+
+    public static ExchangeFilterFunction logResponse() {
+        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
+            if (clientResponse.statusCode().is2xxSuccessful() || clientResponse.statusCode().isSameCodeAs(HttpStatus.NOT_FOUND)) {
+                log.info("✅ Response status: {}", clientResponse.statusCode());
+            } else if (clientResponse.statusCode().is4xxClientError() || clientResponse.statusCode().is5xxServerError()) {
+                log.error("❌  Response status: {}", clientResponse.statusCode());
+            }
+            return Mono.just(clientResponse);
+        });
+    }
+
+    public static ExchangeFilterFunction logRequestHeaders() {
+        return (clientRequest, next) -> {
+            log.info("Request: {} {}", clientRequest.method(), clientRequest.url());
+            clientRequest.headers()
+                    .forEach((name, values) -> {
+                        if (!name.equals(HttpHeaders.AUTHORIZATION)) {
+                            values.forEach(value -> log.debug("{}={}", name, value));
+                        }
+                    });
+            return next.exchange(clientRequest);
+        };
+    }
+
+    public static ExchangeFilterFunction handleNotFoundResponse() {
+        return ExchangeFilterFunction.ofResponseProcessor(response -> {
+            if (response.statusCode() == HttpStatus.NOT_FOUND) {
+                // Create a synthetic successful response (200 OK) with an empty body.
+                return response.bodyToMono(Void.class)
+                        .then(Mono.just(ClientResponse.create(HttpStatus.OK).build()));
+            }
+            return Mono.just(response);
+        });
+    }
+
+    public static ExchangeFilterFunction errorResponseHandler() {
+        return ExchangeFilterFunction.ofResponseProcessor(response -> {
+            if (response.statusCode().is4xxClientError() || response.statusCode().is5xxServerError()) {
+                return response.createException().flatMap(Mono::error);
+            }
+            return Mono.just(response);
+        });
+    }
+}
diff --git a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/service/MaatCourtDataService.java b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/service/MaatCourtDataService.java
index 3093ccba..d10a851a 100644
--- a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/service/MaatCourtDataService.java
+++ b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/service/MaatCourtDataService.java
@@ -2,14 +2,11 @@
 
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.stereotype.Service;
 import uk.gov.justice.laa.crime.common.model.meansassessment.maatapi.MaatApiAssessmentRequest;
 import uk.gov.justice.laa.crime.common.model.meansassessment.maatapi.MaatApiAssessmentResponse;
-import uk.gov.justice.laa.crime.commons.client.RestAPIClient;
 import uk.gov.justice.laa.crime.enums.RequestType;
-import uk.gov.justice.laa.crime.meansassessment.config.MaatApiConfiguration;
+import uk.gov.justice.laa.crime.meansassessment.client.MaatCourtDataApiClient;
 import uk.gov.justice.laa.crime.meansassessment.dto.maatcourtdata.*;
 
 import java.util.Map;
@@ -20,28 +17,17 @@
 public class MaatCourtDataService {
 
     private static final String RESPONSE_STRING = "Response from Court Data API: %s";
-    private final MaatApiConfiguration configuration;
-    @Qualifier("maatApiClient")
-    private final RestAPIClient maatAPIClient;
+
+    private final MaatCourtDataApiClient maatCourtDataApiClient;
 
     public MaatApiAssessmentResponse persistMeansAssessment(MaatApiAssessmentRequest assessment,
                                                             RequestType requestType) {
         log.debug("Request to persist means assessment detail : {}", assessment);
-
         MaatApiAssessmentResponse response;
-        String endpoint = configuration.getFinancialAssessmentEndpoints().getByRequestType(requestType);
         if (RequestType.CREATE.equals(requestType)) {
-            response =
-                    maatAPIClient.post(assessment, new ParameterizedTypeReference<>() {
-                            },
-                            endpoint,
-                            Map.of());
+            response = maatCourtDataApiClient.createFinancialAssessment(assessment);
         } else {
-            response =
-                    maatAPIClient.put(assessment, new ParameterizedTypeReference<>() {
-                            },
-                            endpoint,
-                            Map.of());
+            response = maatCourtDataApiClient.updateFinancialAssessment(assessment);
         }
         log.debug(String.format(RESPONSE_STRING, response));
         return response;
@@ -49,71 +35,42 @@ public MaatApiAssessmentResponse persistMeansAssessment(MaatApiAssessmentRequest
 
     public RepOrderDTO updateCompletionDate(DateCompletionRequestDTO dateCompletionRequestDTO) {
         log.debug("Request to update completion date detail : {}", dateCompletionRequestDTO);
-        RepOrderDTO response = maatAPIClient.post(dateCompletionRequestDTO, new ParameterizedTypeReference<>() {
-                },
-                configuration.getRepOrderEndpoints().getDateCompletionUrl(),
-                Map.of());
+        RepOrderDTO response = maatCourtDataApiClient.updateCompletionDate(dateCompletionRequestDTO);
         log.debug(String.format(RESPONSE_STRING, response));
         return response;
     }
 
     public PassportAssessmentDTO getPassportAssessmentFromRepId(Integer repId) {
-        PassportAssessmentDTO response = maatAPIClient.get(new ParameterizedTypeReference<>() {
-                                                           },
-                configuration.getPassportAssessmentEndpoints().getFindUrl(),
-                repId
-        );
+        PassportAssessmentDTO response = maatCourtDataApiClient.getPassportAssessmentFromRepId(repId);
         log.debug(String.format(RESPONSE_STRING, response));
         return response;
     }
 
     public HardshipReviewDTO getHardshipReviewFromRepId(Integer repId) {
-        HardshipReviewDTO response = maatAPIClient.get(new ParameterizedTypeReference<>() {
-                                                       },
-                configuration.getHardshipReviewEndpoints().getFindUrl(),
-                repId
-        );
+        HardshipReviewDTO response = maatCourtDataApiClient.getHardshipReviewFromRepId(repId);
         log.debug(String.format(RESPONSE_STRING, response));
         return response;
     }
 
     public IOJAppealDTO getIOJAppealFromRepId(Integer repId) {
-        IOJAppealDTO response = maatAPIClient.get(new ParameterizedTypeReference<>() {
-                                                  },
-                configuration.getIojAppealEndpoints().getFindUrl(),
-                repId
-        );
+        IOJAppealDTO response = maatCourtDataApiClient.getIOJAppealFromRepId(repId);
         log.debug(String.format(RESPONSE_STRING, response));
         return response;
     }
 
     public FinancialAssessmentDTO getFinancialAssessment(Integer financialAssessmentId) {
-        FinancialAssessmentDTO response = maatAPIClient.get(new ParameterizedTypeReference<>() {
-                                                            },
-                configuration.getFinancialAssessmentEndpoints().getSearchUrl(),
-                financialAssessmentId
-        );
+        FinancialAssessmentDTO response = maatCourtDataApiClient.getFinancialAssessment(financialAssessmentId);
         log.debug(String.format(RESPONSE_STRING, response));
         return response;
     }
 
     public RepOrderDTO getRepOrder(Integer repId) {
-        RepOrderDTO response = maatAPIClient.get(new ParameterizedTypeReference<>() {
-                                                 },
-                configuration.getRepOrderEndpoints().getFindUrl(),
-                repId
-        );
+        RepOrderDTO response = maatCourtDataApiClient.getRepOrder(repId);
         log.debug(String.format(RESPONSE_STRING, response));
         return response;
     }
 
     public void rollbackFinancialAssessment(Integer financialAssessmentId, Map<String, Object> updateFields) {
-        maatAPIClient.patch(updateFields,
-                new ParameterizedTypeReference<>() {
-                },
-                configuration.getFinancialAssessmentEndpoints().getRollbackUrl(),
-                Map.of(),
-                financialAssessmentId
-        );
+        maatCourtDataApiClient.patchFinancialAssessment(updateFields, financialAssessmentId);
     }
 }
diff --git a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/tracing/TraceIdHandler.java b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/tracing/TraceIdHandler.java
new file mode 100644
index 00000000..12007e3d
--- /dev/null
+++ b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/tracing/TraceIdHandler.java
@@ -0,0 +1,27 @@
+package uk.gov.justice.laa.crime.meansassessment.tracing;
+
+import io.micrometer.tracing.CurrentTraceContext;
+import io.micrometer.tracing.TraceContext;
+import io.micrometer.tracing.Tracer;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Component;
+
+import java.util.Optional;
+
+/**
+ * Helper class for REST API Exception Handler to add the traceId in Error Response.
+ */
+@Component
+@RequiredArgsConstructor
+public class TraceIdHandler {
+
+    private final Tracer tracer;
+
+    public String getTraceId() {
+        return Optional.of(tracer).
+                map(Tracer::currentTraceContext).
+                map(CurrentTraceContext::context).
+                map(TraceContext::traceId).
+                orElse("");
+    }
+}
diff --git a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/validation/service/MeansAssessmentValidationService.java b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/validation/service/MeansAssessmentValidationService.java
index 03d14c5c..b06356ed 100644
--- a/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/validation/service/MeansAssessmentValidationService.java
+++ b/crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/validation/service/MeansAssessmentValidationService.java
@@ -4,11 +4,8 @@
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.stereotype.Service;
-import uk.gov.justice.laa.crime.commons.client.RestAPIClient;
-import uk.gov.justice.laa.crime.meansassessment.config.MaatApiConfiguration;
+import uk.gov.justice.laa.crime.meansassessment.client.MaatCourtDataApiClient;
 import uk.gov.justice.laa.crime.meansassessment.dto.AuthorizationResponseDTO;
 import uk.gov.justice.laa.crime.meansassessment.dto.MeansAssessmentRequestDTO;
 import uk.gov.justice.laa.crime.meansassessment.dto.OutstandingAssessmentResultDTO;
@@ -23,10 +20,8 @@
 @RequiredArgsConstructor
 public class MeansAssessmentValidationService {
 
-    private final MaatApiConfiguration configuration;
     private final MaatCourtDataService maatCourtDataService;
-    @Qualifier("maatApiClient")
-    private final RestAPIClient maatAPIClient;
+    private final MaatCourtDataApiClient maatCourtDataApiClient;
 
     String getUserIdFromRequest(MeansAssessmentRequestDTO meansAssessmentRequest) {
         return meansAssessmentRequest.getUserSession().getUserName();
@@ -34,11 +29,8 @@ String getUserIdFromRequest(MeansAssessmentRequestDTO meansAssessmentRequest) {
 
     public boolean isRoleActionValid(final MeansAssessmentRequestDTO meansAssessmentRequest, String action) {
         if (StringUtils.isNotBlank(getUserIdFromRequest(meansAssessmentRequest)) && StringUtils.isNotBlank(action)) {
-            AuthorizationResponseDTO apiResponse = maatAPIClient.get(new ParameterizedTypeReference<>() {},
-                    configuration.getValidationEndpoints().getRoleActionUrl(),
-                    getUserIdFromRequest(meansAssessmentRequest),
-                    action
-            );
+            AuthorizationResponseDTO apiResponse = maatCourtDataApiClient.getUserRoleAction(
+                    getUserIdFromRequest(meansAssessmentRequest), action);
             return apiResponse.isResult();
         }
         return false;
@@ -46,11 +38,9 @@ public boolean isRoleActionValid(final MeansAssessmentRequestDTO meansAssessment
 
     public boolean isNewWorkReasonValid(final MeansAssessmentRequestDTO meansAssessmentRequest) {
         if (meansAssessmentRequest.getNewWorkReason() != null) {
-            AuthorizationResponseDTO apiResponse = maatAPIClient.get(new ParameterizedTypeReference<>() {},
-                    configuration.getValidationEndpoints().getNewWorkReasonUrl(),
+            AuthorizationResponseDTO apiResponse = maatCourtDataApiClient.getNewWorkReason(
                     getUserIdFromRequest(meansAssessmentRequest),
-                    meansAssessmentRequest.getNewWorkReason().getCode()
-            );
+                    meansAssessmentRequest.getNewWorkReason().getCode());
             return apiResponse.isResult();
         }
         return false;
@@ -58,10 +48,8 @@ public boolean isNewWorkReasonValid(final MeansAssessmentRequestDTO meansAssessm
 
     public boolean isOutstandingAssessment(final MeansAssessmentRequestDTO meansAssessmentRequest) {
         if (meansAssessmentRequest.getRepId() != null) {
-            OutstandingAssessmentResultDTO apiResponse = maatAPIClient.get(new ParameterizedTypeReference<>() {},
-                    configuration.getValidationEndpoints().getOutstandingAssessmentsUrl(),
-                    meansAssessmentRequest.getRepId()
-            );
+            OutstandingAssessmentResultDTO apiResponse = maatCourtDataApiClient.getOutstandingAssessment(
+                    meansAssessmentRequest.getRepId());
             return apiResponse.isOutstandingAssessments();
         }
         return false;
@@ -71,12 +59,10 @@ public boolean isRepOrderReserved(final MeansAssessmentRequestDTO meansAssessmen
         if (StringUtils.isNotBlank(getUserIdFromRequest(meansAssessmentRequest))
                 && StringUtils.isNotBlank(meansAssessmentRequest.getUserSession().getSessionId())
                 && meansAssessmentRequest.getRepId() != null) {
-            AuthorizationResponseDTO apiResponse = maatAPIClient.get(new ParameterizedTypeReference<>() {},
-                    configuration.getValidationEndpoints().getReservationsUrl(),
+            AuthorizationResponseDTO apiResponse = maatCourtDataApiClient.getReservationDetail(
                     getUserIdFromRequest(meansAssessmentRequest),
                     meansAssessmentRequest.getRepId(),
-                    meansAssessmentRequest.getUserSession().getSessionId()
-            );
+                    meansAssessmentRequest.getUserSession().getSessionId());
             return apiResponse.isResult();
         }
         return false;
diff --git a/crime-means-assessment/src/main/resources/application.yaml b/crime-means-assessment/src/main/resources/application.yaml
index 79dd6b40..08fec1a4 100644
--- a/crime-means-assessment/src/main/resources/application.yaml
+++ b/crime-means-assessment/src/main/resources/application.yaml
@@ -17,33 +17,12 @@ management:
   tracing:
     propagation:
       type: w3c,b3
+    enabled: true
 
-maatApi:
-  oAuthEnabled: true
-  baseUrl: ${MAAT_API_BASE_URL}
-  assessments-domain: ${maatApi.baseUrl}/api/internal/v1/assessment
-
-  post-processing-url: ${maatApi.assessments-domain}/post-processing
-
-  validation-endpoints:
-    role-action-url: ${maatApi.assessments-domain}/authorization/users/{username}/actions/{action}
-    new-work-reason-url: ${maatApi.assessments-domain}/authorization/users/{username}/work-reasons/{nworCode}
-    reservations-url: ${maatApi.assessments-domain}/authorization/users/{username}/reservations/{reservationId}/sessions/{sessionId}
-    outstanding-assessments-url: ${maatApi.assessments-domain}/financial-assessments/check-outstanding/{repId}
-  financial-assessment-endpoints:
-    create-url: ${maatApi.assessments-domain}/financial-assessments
-    update-url: ${maatApi.assessments-domain}/financial-assessments
-    search-url: ${maatApi.assessments-domain}/financial-assessments/{financialAssessmentId}
-    rollback-url: ${maatApi.assessments-domain}/financial-assessments/rollback/{financialAssessmentId}
-  passport-assessment-endpoints:
-    find-url: ${maatApi.assessments-domain}/passport-assessments/repId/{repId}
-  hardship-review-endpoints:
-    find-url: ${maatApi.assessments-domain}/hardship/repId/{repId}
-  ioj-appeal-endpoints:
-    find-url: ${maatApi.assessments-domain}/ioj-appeal/repId/{repId}
-  rep-order-endpoints:
-    find-url: ${maatApi.assessments-domain}/rep-orders/{repId}
-    date-completion-url: ${maatApi.assessments-domain}/rep-orders/update-date-completed
+services:
+  maat-api:
+    baseUrl: ${MAAT_API_BASE_URL}
+    registrationId: maat-api
 
 spring:
   datasource:
@@ -72,6 +51,30 @@ spring:
         jwt:
           issuer-uri: ${JWT_ISSUER_URI}
 
+zipkin:
+  tracing:
+    export:
+      enabled: false
+
+resilience4j:
+  retry:
+    configs:
+      default:
+        max-attempts: 3
+        wait-duration: 2s
+        enableExponentialBackoff: true
+        exponentialBackoffMultiplier: 2
+        retry-exceptions:
+          - org.springframework.web.reactive.function.client.WebClientRequestException
+          - org.springframework.web.reactive.function.client.WebClientResponseException.BadGateway
+          - org.springframework.web.reactive.function.client.WebClientResponseException.TooManyRequests
+          - org.springframework.web.reactive.function.client.WebClientResponseException.ServiceUnavailable
+          - org.springframework.web.reactive.function.client.WebClientResponseException.GatewayTimeout
+
+    instances:
+      maatCourtDataWebClient:
+        base-config: default
+
   liquibase:
     change-log: classpath:/db/changelog/db.changelog-master.yaml
 
@@ -102,9 +105,4 @@ sentry:
   attach-stacktrace: true
   sample-rate: ${SENTRY_SAMPLE_RATE}
 
-retry-config:
-  max-retries: 3
-  min-back-off-period: 5
-  jitter-value: 0.75
-
 version: 0.0.1
diff --git a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/config/MaatApiConfigurationTest.java b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/config/MaatApiConfigurationTest.java
deleted file mode 100644
index 3677c807..00000000
--- a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/config/MaatApiConfigurationTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package uk.gov.justice.laa.crime.meansassessment.config;
-
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit.jupiter.SpringExtension;
-import uk.gov.justice.laa.crime.enums.RequestType;
-
-import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
-
-@ExtendWith(SpringExtension.class)
-@EnableConfigurationProperties(value = MaatApiConfiguration.class)
-@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)
-class MaatApiConfigurationTest {
-
-    @Autowired
-    @Qualifier("test_configuration")
-    private MaatApiConfiguration configuration;
-
-    @Test
-    void givenUserDefinedPOJO_whenBindingYMLConfigFile_thenAllFieldsAreSet() {
-        assertThat(buildUrl("search-url")).isEqualTo(configuration.getFinancialAssessmentEndpoints().getSearchUrl());
-        assertThat(buildUrl("create-url")).isEqualTo(configuration.getFinancialAssessmentEndpoints().getCreateUrl());
-        assertThat(buildUrl("update-url")).isEqualTo(configuration.getFinancialAssessmentEndpoints().getUpdateUrl());
-        assertThat(buildUrl("rollback-url")).isEqualTo(configuration.getFinancialAssessmentEndpoints().getRollbackUrl());
-
-        assertThat(false).isEqualTo(configuration.isOAuthEnabled());
-
-        assertThat(buildUrl("role-action-url")).isEqualTo(configuration.getValidationEndpoints().getRoleActionUrl());
-        assertThat(buildUrl("new-work-reason-url")).isEqualTo(configuration.getValidationEndpoints().getNewWorkReasonUrl());
-        assertThat(buildUrl("reservation-url")).isEqualTo(configuration.getValidationEndpoints().getReservationsUrl());
-        assertThat(buildUrl("outstanding-assessments-url")).isEqualTo(configuration.getValidationEndpoints().getOutstandingAssessmentsUrl());
-    }
-
-    @Test
-    void givenDefinedFinancialAssessmentEndpoints_whenGetByRequestTypeIsInvoked_thenCorrectEndpointIsReturned() {
-        assertThat(buildUrl("create-url")).isEqualTo(configuration.getFinancialAssessmentEndpoints().getByRequestType(RequestType.CREATE));
-    }
-
-    @Test
-    void givenDefinedPostProcessingUrl_whenGetPostProcessingUrl_thenCorrectEndpointIsReturned() {
-        assertThat(buildUrl("post-processing/{repId}")).isEqualTo(configuration.getPostProcessingUrl());
-    }
-
-    private String buildUrl(String url) {
-        return String.format("http://localhost:9999/api/internal/v1/assessment/%s", url);
-    }
-
-    @Configuration
-    static class MaatApiConfigurationFactory {
-        @Bean(name = "test_configuration")
-        MaatApiConfiguration getDefaultConfiguration() {
-            return new MaatApiConfiguration();
-        }
-    }
-}
diff --git a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/config/ServicesConfigurationTest.java b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/config/ServicesConfigurationTest.java
new file mode 100644
index 00000000..a808d64f
--- /dev/null
+++ b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/config/ServicesConfigurationTest.java
@@ -0,0 +1,39 @@
+package uk.gov.justice.laa.crime.meansassessment.config;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+
+@ExtendWith(SpringExtension.class)
+@EnableConfigurationProperties(value = ServicesConfiguration.class)
+@ContextConfiguration(initializers = ConfigDataApplicationContextInitializer.class)
+class ServicesConfigurationTest {
+
+    @Autowired
+    @Qualifier("test_configuration")
+    private ServicesConfiguration configuration;
+
+    @Test
+    void givenUserDefinedPOJO_whenBindingYMLConfigFile_thenAllFieldsAreSet() {
+
+        assertThat("http://localhost:9999/api/internal/v1/assessment").isEqualTo(configuration.getMaatApi().getBaseUrl());
+        assertThat("maat-api").isEqualTo(configuration.getMaatApi().getRegistrationId());
+    }
+
+    @Configuration
+    static class MaatApiConfigurationFactory {
+        @Bean(name = "test_configuration")
+        ServicesConfiguration getDefaultConfiguration() {
+            return new ServicesConfiguration();
+        }
+    }
+}
diff --git a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/MeansAssessmentControllerTest.java b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/MeansAssessmentControllerTest.java
index d03903cf..f275d8c3 100644
--- a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/MeansAssessmentControllerTest.java
+++ b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/MeansAssessmentControllerTest.java
@@ -6,22 +6,22 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
 import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 import org.springframework.test.web.servlet.MockMvc;
 import uk.gov.justice.laa.crime.common.model.meansassessment.ApiCreateMeansAssessmentRequest;
 import uk.gov.justice.laa.crime.common.model.meansassessment.ApiGetMeansAssessmentResponse;
 import uk.gov.justice.laa.crime.common.model.meansassessment.ApiUpdateMeansAssessmentRequest;
-import uk.gov.justice.laa.crime.commons.tracing.TraceIdHandler;
-import uk.gov.justice.laa.crime.enums.*;
+import uk.gov.justice.laa.crime.enums.RequestType;
 import uk.gov.justice.laa.crime.meansassessment.builder.MeansAssessmentRequestDTOBuilder;
 import uk.gov.justice.laa.crime.meansassessment.data.builder.TestModelDataBuilder;
 import uk.gov.justice.laa.crime.meansassessment.dto.MeansAssessmentRequestDTO;
 import uk.gov.justice.laa.crime.meansassessment.service.AssessmentCriteriaService;
 import uk.gov.justice.laa.crime.meansassessment.service.MeansAssessmentService;
+import uk.gov.justice.laa.crime.meansassessment.tracing.TraceIdHandler;
 import uk.gov.justice.laa.crime.meansassessment.validation.validator.MeansAssessmentValidationProcessor;
 
 import java.math.BigDecimal;
@@ -49,19 +49,19 @@ class MeansAssessmentControllerTest {
     @Autowired
     private ObjectMapper objectMapper;
 
-    @MockBean
+    @MockitoBean
     private MeansAssessmentRequestDTOBuilder assessmentRequestDTOBuilder;
 
-    @MockBean
+    @MockitoBean
     private MeansAssessmentService meansAssessmentService;
 
-    @MockBean
+    @MockitoBean
     private AssessmentCriteriaService assessmentCriteriaService;
 
-    @MockBean
+    @MockitoBean
     private MeansAssessmentValidationProcessor assessmentValidator;
 
-    @MockBean
+    @MockitoBean
     private TraceIdHandler traceIdHandler;
 
     @Test
diff --git a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/MeansAssessmentIntegrationTest.java b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/MeansAssessmentIntegrationTest.java
index e7e1139f..3dd537d6 100644
--- a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/MeansAssessmentIntegrationTest.java
+++ b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/MeansAssessmentIntegrationTest.java
@@ -55,7 +55,14 @@
 class MeansAssessmentIntegrationTest {
 
     private static final boolean IS_VALID = true;
-    private static final String ENDPOINT_URL = "/api/internal/v1/assessment/means";
+
+    private static final String API_VERSION = "/api/internal/v1/assessment";
+    private static final String ENDPOINT_URL = API_VERSION + "/means";
+    private static final String AUTHORIZATION_ENDPOINT = API_VERSION + "/authorization";
+
+    private static final String FINANCIAL_ASSESSMENT_ENDPOINT = API_VERSION + "/financial-assessments";
+
+    private static final String REP_ORDER_ENDPOINT = API_VERSION + "/rep-orders";
 
     private MockMvc mvc;
 
@@ -121,12 +128,12 @@ private MockHttpServletRequestBuilder buildRequest(HttpMethod method, String con
     }
 
     private void stubForRoleActionAndReservationUrl() {
-        stubFor(get(urlEqualTo("/api/internal/v1/assessment/role-action-url")).willReturn(aResponse()
+        stubFor(get(urlPathMatching(AUTHORIZATION_ENDPOINT + "/users/([a-zA-Z]*)/actions/([a-zA-Z_]*)")).willReturn(aResponse()
                 .withStatus(200)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))
                 .withBody("true")));
 
-        stubFor(get(urlEqualTo("/api/internal/v1/assessment/reservation-url")).willReturn(aResponse()
+        stubFor(get(urlPathMatching(AUTHORIZATION_ENDPOINT + "/users/([a-zA-Z]*)/reservations/([0-9]*)/sessions/([a-zA-Z0-9_.-]*)")).willReturn(aResponse()
                 .withStatus(200)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))
                 .withBody("true")));
@@ -164,17 +171,17 @@ void givenAValidCreateMeansAssessmentRequest_whenDateCompletionCallFails_thenSer
 
         stubForRoleActionAndReservationUrl();
 
-        stubFor(get(urlEqualTo("/api/internal/v1/assessment/outstanding-assessments-url")).willReturn(aResponse()
+        stubFor(get(urlPathMatching(FINANCIAL_ASSESSMENT_ENDPOINT + "/check-outstanding/([0-9]*)")).willReturn(aResponse()
                 .withStatus(200)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))
                 .withBody(objectMapper.writeValueAsString(new OutstandingAssessmentResultDTO()))));
 
-        stubFor(get(urlEqualTo("/api/internal/v1/assessment/new-work-reason-url")).willReturn(aResponse()
+        stubFor(get(urlPathMatching(AUTHORIZATION_ENDPOINT + "/users/([a-zA-Z]*)/work-reasons/([a-zA-Z]*)")).willReturn(aResponse()
                 .withStatus(200)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))
                 .withBody("true")));
 
-        stubFor(post(urlEqualTo("/api/internal/v1/assessment/rep-orders/update-date-completed")).willReturn(aResponse()
+        stubFor(post(urlPathMatching(REP_ORDER_ENDPOINT + "/update-date-completed")).willReturn(aResponse()
                 .withStatus(504)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))));
 
@@ -183,8 +190,8 @@ void givenAValidCreateMeansAssessmentRequest_whenDateCompletionCallFails_thenSer
                 .andExpect(content().contentType(MediaType.APPLICATION_JSON));
 
         verify(exactly(1), postRequestedFor(urlEqualTo("/oauth2/token")));
-        verify(exactly(4), getRequestedFor(urlPathMatching("/api/internal/v1/assessment/.*")));
-        verify(exactly(3), postRequestedFor(urlEqualTo("/api/internal/v1/assessment/rep-orders/update-date-completed")));
+        verify(exactly(4), getRequestedFor(urlPathMatching(API_VERSION + "/.*")));
+        verify(exactly(3), postRequestedFor(urlEqualTo(REP_ORDER_ENDPOINT + "/update-date-completed")));
     }
 
     @Test
@@ -194,23 +201,23 @@ void givenAValidCreateMeansAssessmentRequest_WhenCreateAssessmentInvoked_ThenSuc
 
         stubForRoleActionAndReservationUrl();
 
-        stubFor(get(urlEqualTo("/api/internal/v1/assessment/outstanding-assessments-url")).willReturn(aResponse()
+        stubFor(get(urlPathMatching(FINANCIAL_ASSESSMENT_ENDPOINT + "/check-outstanding/([0-9]*)")).willReturn(aResponse()
                 .withStatus(200)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))
                 .withBody(objectMapper.writeValueAsString(new OutstandingAssessmentResultDTO()))));
 
-        stubFor(get(urlEqualTo("/api/internal/v1/assessment/new-work-reason-url")).willReturn(aResponse()
+        stubFor(get(urlPathMatching(AUTHORIZATION_ENDPOINT + "/users/([a-zA-Z]*)/work-reasons/([a-zA-Z]*)")).willReturn(aResponse()
                 .withStatus(200)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))
                 .withBody("true")));
 
-        stubFor(post(urlEqualTo("/api/internal/v1/assessment/rep-orders/update-date-completed")).atPriority(1).willReturn(aResponse()
+        stubFor(post(urlEqualTo(REP_ORDER_ENDPOINT + "/update-date-completed")).atPriority(1).willReturn(aResponse()
                 .withStatus(200)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))
                 .withBody(objectMapper.writeValueAsString(RepOrderDTO.builder().dateModified(LocalDateTime.now()).build()))));
 
         // MaatCourtDataService - Persist means assessment
-        stubFor(post(urlEqualTo("/api/internal/v1/assessment/create-url")).willReturn(aResponse()
+        stubFor(post(urlEqualTo(FINANCIAL_ASSESSMENT_ENDPOINT)).willReturn(aResponse()
                 .withStatus(200)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))
                 .withBody(objectMapper.writeValueAsString(TestModelDataBuilder.getMaatApiInitAssessmentResponse()))));
@@ -219,9 +226,9 @@ void givenAValidCreateMeansAssessmentRequest_WhenCreateAssessmentInvoked_ThenSuc
                 .andExpect(status().isOk())
                 .andExpect(content().contentType(MediaType.APPLICATION_JSON));
 
-        verify(exactly(4), getRequestedFor(urlPathMatching("/api/internal/v1/assessment/.*")));
-        verify(exactly(1), postRequestedFor(urlEqualTo("/api/internal/v1/assessment/rep-orders/update-date-completed")));
-        verify(exactly(1), postRequestedFor(urlEqualTo("/api/internal/v1/assessment/create-url")));
+        verify(exactly(4), getRequestedFor(urlPathMatching(API_VERSION + "/.*")));
+        verify(exactly(1), postRequestedFor(urlEqualTo(REP_ORDER_ENDPOINT + "/update-date-completed")));
+        verify(exactly(1), postRequestedFor(urlEqualTo(FINANCIAL_ASSESSMENT_ENDPOINT)));
     }
 
     @Test
@@ -244,19 +251,19 @@ void givenAValidUpdateMeansAssessmentRequest_whenUpdateAssessmentInvoked_ThenSuc
 
         stubForRoleActionAndReservationUrl();
 
-        stubFor(get(urlMatching("/api/internal/v1/assessment/rep-orders/.*")).willReturn(aResponse()
+        stubFor(get(urlMatching(REP_ORDER_ENDPOINT + "/.*")).willReturn(aResponse()
                 .withStatus(200)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))
                 .withBody(objectMapper.writeValueAsString(TestModelDataBuilder.getFinancialAssessmentDTO()))));
 
         // DateCompletionService - retrieve rep order
-        stubFor(post(urlEqualTo("/api/internal/v1/assessment/rep-orders/update-date-completed")).willReturn(aResponse()
+        stubFor(post(urlEqualTo(REP_ORDER_ENDPOINT + "/update-date-completed")).willReturn(aResponse()
                 .withStatus(200)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))
                 .withBody(objectMapper.writeValueAsString(RepOrderDTO.builder().dateModified(LocalDateTime.now()).build()))));
 
         // MaatCourtDataService - Persist means assessment
-        stubFor(put(urlEqualTo("/api/internal/v1/assessment/update-url")).willReturn(aResponse()
+        stubFor(put(urlEqualTo(FINANCIAL_ASSESSMENT_ENDPOINT)).willReturn(aResponse()
                 .withStatus(200)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))
                 .withBody(objectMapper.writeValueAsString(TestModelDataBuilder.getMaatApiFullAssessmentResponse()))));
@@ -265,9 +272,9 @@ void givenAValidUpdateMeansAssessmentRequest_whenUpdateAssessmentInvoked_ThenSuc
                 .andExpect(status().isOk())
                 .andExpect(content().contentType(MediaType.APPLICATION_JSON));
 
-        verify(exactly(3), getRequestedFor(urlPathMatching("/api/internal/v1/assessment/.*")));
-        verify(exactly(1), postRequestedFor(urlEqualTo("/api/internal/v1/assessment/rep-orders/update-date-completed")));
-        verify(exactly(1), putRequestedFor(urlEqualTo("/api/internal/v1/assessment/update-url")));
+        verify(exactly(3), getRequestedFor(urlPathMatching(API_VERSION + "/.*")));
+        verify(exactly(1), postRequestedFor(urlEqualTo(REP_ORDER_ENDPOINT + "/update-date-completed")));
+        verify(exactly(1), putRequestedFor(urlEqualTo(FINANCIAL_ASSESSMENT_ENDPOINT)));
     }
 
     @Test
@@ -302,7 +309,7 @@ void givenAValidFinancialAssessmentId_whenGetOldAssessmentInvoked_thenAssessment
         financialAssessmentDTO.setFinAssIncomeEvidences(finAssIncomeEvidenceDTOList);
         financialAssessmentDTO.setUpdated(TestModelDataBuilder.TEST_DATE_CREATED);
 
-        stubFor(get(urlEqualTo("/api/internal/v1/assessment/search-url")).willReturn(aResponse()
+        stubFor(get(urlEqualTo(API_VERSION + "/search-url")).willReturn(aResponse()
                 .withStatus(200)
                 .withHeader("Content-Type", String.valueOf(MediaType.APPLICATION_JSON))
                 .withBody(objectMapper.writeValueAsString(financialAssessmentDTO))));
@@ -310,7 +317,7 @@ void givenAValidFinancialAssessmentId_whenGetOldAssessmentInvoked_thenAssessment
         mvc.perform(buildRequest(HttpMethod.GET, ENDPOINT_URL + "/" + TestModelDataBuilder.MEANS_ASSESSMENT_ID, Boolean.TRUE))
                 .andExpect(status().isOk());
 
-        verify(exactly(1), getRequestedFor(urlPathMatching("/api/internal/v1/assessment/search-url")));
+        verify(exactly(1), getRequestedFor(urlPathMatching(API_VERSION + "/search-url")));
     }
 
 }
\ No newline at end of file
diff --git a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/StatelessMeansAssessmentControllerTest.java b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/StatelessMeansAssessmentControllerTest.java
index 4003c364..fc08de53 100644
--- a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/StatelessMeansAssessmentControllerTest.java
+++ b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/StatelessMeansAssessmentControllerTest.java
@@ -6,15 +6,14 @@
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
-import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.http.HttpMethod;
 import org.springframework.http.MediaType;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 import org.springframework.test.web.servlet.MockMvc;
 import uk.gov.justice.laa.crime.common.model.meansassessment.ApiMeansAssessmentRequest;
 import uk.gov.justice.laa.crime.common.model.meansassessment.stateless.Assessment;
 import uk.gov.justice.laa.crime.common.model.meansassessment.stateless.StatelessApiRequest;
-import uk.gov.justice.laa.crime.commons.tracing.TraceIdHandler;
 import uk.gov.justice.laa.crime.enums.Frequency;
 import uk.gov.justice.laa.crime.enums.FullAssessmentResult;
 import uk.gov.justice.laa.crime.enums.InitAssessmentResult;
@@ -25,7 +24,9 @@
 import uk.gov.justice.laa.crime.meansassessment.*;
 import uk.gov.justice.laa.crime.meansassessment.data.builder.TestModelDataBuilder;
 import uk.gov.justice.laa.crime.meansassessment.service.AssessmentCriteriaService;
-import uk.gov.justice.laa.crime.meansassessment.service.stateless.*;
+import uk.gov.justice.laa.crime.meansassessment.service.stateless.StatelessAssessmentService;
+import uk.gov.justice.laa.crime.meansassessment.service.stateless.StatelessResult;
+import uk.gov.justice.laa.crime.meansassessment.tracing.TraceIdHandler;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
@@ -55,13 +56,13 @@ class StatelessMeansAssessmentControllerTest {
     @Autowired
     private ObjectMapper objectMapper;
 
-    @MockBean
+    @MockitoBean
     private StatelessAssessmentService statelessAssessmentService;
 
-    @MockBean
+    @MockitoBean
     private AssessmentCriteriaService assessmentCriteriaService;
 
-    @MockBean
+    @MockitoBean
     private TraceIdHandler traceIdHandler;
 
     @Test
diff --git a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/filter/Resilience4jRetryFilterTest.java b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/filter/Resilience4jRetryFilterTest.java
new file mode 100644
index 00000000..4b5475dc
--- /dev/null
+++ b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/filter/Resilience4jRetryFilterTest.java
@@ -0,0 +1,242 @@
+package uk.gov.justice.laa.crime.meansassessment.filter;
+
+import io.github.resilience4j.retry.Retry;
+import io.github.resilience4j.retry.RetryConfig;
+import io.github.resilience4j.retry.RetryRegistry;
+import lombok.extern.slf4j.Slf4j;
+import org.assertj.core.api.SoftAssertions;
+import org.assertj.core.api.junit.jupiter.InjectSoftAssertions;
+import org.assertj.core.api.junit.jupiter.SoftAssertionsExtension;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.reactive.function.client.*;
+import reactor.core.publisher.Mono;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.LinkedList;
+
+import static org.mockito.Mockito.when;
+
+@Slf4j
+@ExtendWith(MockitoExtension.class)
+@ExtendWith(SoftAssertionsExtension.class)
+class Resilience4jRetryFilterTest {
+
+    @InjectSoftAssertions
+    private SoftAssertions softly;
+
+    @Mock
+    private ExchangeFunction exchangeFunction;
+
+    private static final int NUM_RETRIES = 3;
+    private static RetryRegistry retryRegistry;
+    public static final String DEFAULT_CONFIG_NAME = "default";
+    private static final URI DEFAULT_URL = URI.create("https://example.com");
+
+    @BeforeEach
+    void setupConfiguration() {
+        RetryConfig retryConfig = RetryConfig.custom()
+                .maxAttempts(NUM_RETRIES)
+                .retryExceptions(WebClientRequestException.class,
+                                 WebClientResponseException.BadGateway.class,
+                                 WebClientResponseException.TooManyRequests.class,
+                                 WebClientResponseException.NotFound.class
+                )
+                .failAfterMaxAttempts(true)
+                .build();
+        retryRegistry = RetryRegistry.of(retryConfig);
+    }
+
+    @Test
+    void givenRetriesExhausted_whenRetryFilterIsInvoked_thenFinalExceptionIsThrown() {
+        ClientRequest request = ClientRequest.create(HttpMethod.GET, DEFAULT_URL).build();
+        LinkedList<RuntimeException> errors = new LinkedList<>(
+                Arrays.asList(getWebClientResponseException(HttpStatus.NOT_FOUND),
+                              getWebClientResponseException(HttpStatus.TOO_MANY_REQUESTS),
+                              getWebClientResponseException(HttpStatus.BAD_GATEWAY)
+                )
+        );
+        Mono<ClientResponse> errorMono = getClientResponseMono(errors);
+
+        when(exchangeFunction.exchange(request))
+                .thenReturn(errorMono);
+
+        Mono<ClientResponse> response =
+                new Resilience4jRetryFilter(retryRegistry, DEFAULT_CONFIG_NAME)
+                        .filter(request, exchangeFunction);
+
+        softly.assertThatThrownBy(
+                        response::block
+                ).isInstanceOf(WebClientResponseException.BadGateway.class)
+                .hasMessageContaining("502 Bad Gateway");
+
+        verifyCorrectNumberOfCalls(NUM_RETRIES, DEFAULT_CONFIG_NAME);
+        softly.assertAll();
+    }
+
+    @Test
+    void givenSuccessfulResponseWithoutRetry_whenRetryFilterIsInvoked_thenOkResponseIsReturned() {
+        ClientRequest request = ClientRequest.create(HttpMethod.GET, DEFAULT_URL).build();
+        Mono<ClientResponse> responseMono = Mono.just(ClientResponse.create(HttpStatus.OK).build());
+
+        when(exchangeFunction.exchange(request))
+                .thenReturn(responseMono);
+
+        Mono<ClientResponse> clientResponse =
+                new Resilience4jRetryFilter(retryRegistry, DEFAULT_CONFIG_NAME)
+                        .filter(request, exchangeFunction);
+
+        ClientResponse response = clientResponse.block();
+
+        verifyCorrectNumberOfCalls(1, DEFAULT_CONFIG_NAME);
+        softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
+        softly.assertAll();
+    }
+
+    @Test
+    void givenSuccessfulResponseFollowingRetry_whenRetryFilterIsInvoked_thenOkResponseIsReturned() {
+        ClientRequest request = ClientRequest.create(HttpMethod.GET, DEFAULT_URL).build();
+        LinkedList<RuntimeException> errors = new LinkedList<>(
+                Arrays.asList(getWebClientResponseException(HttpStatus.TOO_MANY_REQUESTS),
+                              getWebClientResponseException(HttpStatus.BAD_GATEWAY)
+                )
+        );
+
+        Mono<ClientResponse> responseMono =
+                getClientResponseMono(errors);
+
+        when(exchangeFunction.exchange(request))
+                .thenReturn(responseMono);
+
+        Mono<ClientResponse> clientResponse =
+                new Resilience4jRetryFilter(retryRegistry, DEFAULT_CONFIG_NAME)
+                        .filter(request, exchangeFunction);
+
+        ClientResponse response = clientResponse.block();
+
+        verifyCorrectNumberOfCalls(NUM_RETRIES, DEFAULT_CONFIG_NAME);
+        softly.assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
+        softly.assertAll();
+    }
+
+    private void verifyCorrectNumberOfCalls(int numRetries, String configName) {
+        Retry retry = retryRegistry.retry(configName);
+        long numberOfTotalCalls = retry.getMetrics().getNumberOfTotalCalls();
+        softly.assertThat(numberOfTotalCalls).isEqualTo(numRetries);
+    }
+
+    @Test
+    void givenUnRetryableException_whenRetryFilterIsInvoked_thenNoRetriesAreAttempted() {
+        ClientRequest request = ClientRequest.create(HttpMethod.GET, DEFAULT_URL).build();
+
+        when(exchangeFunction.exchange(request))
+                .thenReturn(Mono.error(getWebClientResponseException(HttpStatus.UNAUTHORIZED)));
+
+        Mono<ClientResponse> response =
+                new Resilience4jRetryFilter(retryRegistry, DEFAULT_CONFIG_NAME)
+                        .filter(request, exchangeFunction);
+
+        softly.assertThatThrownBy(
+                        response::block
+                ).isInstanceOf(WebClientResponseException.class)
+                .hasMessageContaining("401 Unauthorized");
+
+        verifyCorrectNumberOfCalls(1, DEFAULT_CONFIG_NAME);
+        softly.assertAll();
+    }
+
+    @Test
+    void givenOverrideConfiguration_whenRetryFilterIsInvoked_thenCorrectConfigurationIsApplied() {
+        RetryConfig retryConfig = RetryConfig.custom()
+                .maxAttempts(2)
+                .retryExceptions(WebClientResponseException.Conflict.class)
+                .failAfterMaxAttempts(true)
+                .build();
+        retryRegistry.retry("override", retryConfig);
+
+        ClientRequest request = ClientRequest.create(HttpMethod.GET, DEFAULT_URL).build();
+
+        LinkedList<RuntimeException> errors = new LinkedList<>(
+                Arrays.asList(getWebClientResponseException(HttpStatus.CONFLICT),
+                              getWebClientResponseException(HttpStatus.CONFLICT),
+                              getWebClientResponseException(HttpStatus.CONFLICT)
+                )
+        );
+
+        Mono<ClientResponse> errorMono = getClientResponseMono(errors);
+
+        when(exchangeFunction.exchange(request))
+                .thenReturn(errorMono);
+
+        Mono<ClientResponse> response =
+                new Resilience4jRetryFilter(retryRegistry, "override")
+                        .filter(request, exchangeFunction);
+
+        softly.assertThatThrownBy(
+                        response::block
+                ).isInstanceOf(WebClientResponseException.Conflict.class)
+                .hasMessageContaining("409 Conflict");
+
+        verifyCorrectNumberOfCalls(2, "override");
+        softly.assertAll();
+    }
+
+    @Test
+    void givenMissingOverrideConfiguration_whenRetryFilterIsInvoked_thenDefaultConfigurationIsApplied() {
+        ClientRequest request = ClientRequest.create(HttpMethod.GET, DEFAULT_URL).build();
+
+        LinkedList<RuntimeException> errors = new LinkedList<>(
+                Arrays.asList(getWebClientResponseException(HttpStatus.BAD_GATEWAY),
+                              getWebClientResponseException(HttpStatus.CONFLICT),
+                              getWebClientResponseException(HttpStatus.TOO_MANY_REQUESTS)
+                )
+        );
+
+        Mono<ClientResponse> errorMono = getClientResponseMono(errors);
+
+        when(exchangeFunction.exchange(request))
+                .thenReturn(errorMono);
+
+        Mono<ClientResponse> response =
+                new Resilience4jRetryFilter(retryRegistry, "override")
+                        .filter(request, exchangeFunction);
+
+        softly.assertThatThrownBy(
+                        response::block
+                ).isInstanceOf(WebClientResponseException.Conflict.class)
+                .hasMessageContaining("409 Conflict");
+
+        verifyCorrectNumberOfCalls(2, DEFAULT_CONFIG_NAME);
+        softly.assertAll();
+    }
+
+    private static WebClientResponseException getWebClientResponseException(HttpStatus status) {
+        return WebClientResponseException.create(
+                status.value(),
+                status.getReasonPhrase(),
+                new HttpHeaders(),
+                new byte[0],
+                null
+        );
+    }
+
+    private static Mono<ClientResponse> getClientResponseMono(LinkedList<RuntimeException> errors) {
+        return Mono.defer(
+                () -> {
+                    if (errors.isEmpty()) {
+                        return Mono.just(ClientResponse.create(HttpStatus.OK).build());
+                    }
+                    Throwable error = errors.pop();
+                    log.info("Simulating failure: {}", error.getClass().getSimpleName());
+                    return Mono.error(error);
+                }
+        );
+    }
+}
diff --git a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/filter/WebClientFiltersTest.java b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/filter/WebClientFiltersTest.java
new file mode 100644
index 00000000..85faa4e0
--- /dev/null
+++ b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/filter/WebClientFiltersTest.java
@@ -0,0 +1,125 @@
+package uk.gov.justice.laa.crime.meansassessment.filter;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.reactive.function.client.ClientRequest;
+import org.springframework.web.reactive.function.client.ClientResponse;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+import reactor.core.publisher.Mono;
+
+import java.net.URI;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+class WebClientFiltersTest {
+
+    public static final ClientRequest CLIENT_REQUEST =
+            ClientRequest.create(HttpMethod.GET, URI.create("https://example.com")).build();
+
+    @Test
+    void givenRequestWithHeaders_whenLogRequestHeadersFilterApplied_thenResponseIsPassedThrough() {
+        // given
+        ClientResponse dummyResponse = ClientResponse.create(HttpStatus.OK).build();
+
+        // Use an AtomicReference to capture the request passed along the filter chain.
+        AtomicReference<ClientRequest> capturedRequest = new AtomicReference<>();
+
+        // when
+        Mono<ClientResponse> result = WebClientFilters.logRequestHeaders()
+                .filter(CLIENT_REQUEST, req -> {
+                    capturedRequest.set(req);
+                    return Mono.just(dummyResponse);
+                });
+        ClientResponse response = result.block();
+
+        // then
+        assertThat(capturedRequest.get()).isEqualTo(CLIENT_REQUEST);
+        // Also verify that the response is passed through unchanged.
+        assertThat(response).isNotNull();
+        assertThat(response.statusCode()).isEqualTo(HttpStatus.OK);
+    }
+
+    @Test
+    void givenClientResponse_whenLogResponseFilterApplied_thenResponseIsUnchanged() {
+        // given
+        ClientResponse response =
+                ClientResponse.create(HttpStatus.OK)
+                        .header("X-Test", "headerValue")
+                        .build();
+
+        // when
+        Mono<ClientResponse> result = WebClientFilters.logResponse()
+                .filter(CLIENT_REQUEST, req -> Mono.just(response));
+        ClientResponse filteredResponse = result.block();
+
+        // then
+        assertThat(filteredResponse).isNotNull();
+        assertThat(filteredResponse.statusCode()).isEqualTo(HttpStatus.OK);
+    }
+
+    @Test
+    void givenSuccessResponse_whenErrorResponseHandlerApplied_thenResponseIsPassedThrough() {
+        // given
+        ClientResponse successResponse = ClientResponse.create(HttpStatus.OK).build();
+
+        // when
+        Mono<ClientResponse> result = WebClientFilters.errorResponseHandler()
+                .filter(CLIENT_REQUEST, req -> Mono.just(successResponse));
+        ClientResponse filteredResponse = result.block();
+
+        // then
+        assertThat(filteredResponse).isNotNull();
+        assertThat(filteredResponse.statusCode()).isEqualTo(HttpStatus.OK);
+    }
+
+    @ParameterizedTest
+    @ValueSource(ints = {400, 500})
+    void givenErrorResponse_whenErrorResponseHandlerApplied_thenThrowsWebClientResponseException(int statusCode) {
+        // given
+        ClientResponse errorResponse = ClientResponse.create(HttpStatus.valueOf(statusCode)).build();
+        // when
+        Mono<ClientResponse> result = WebClientFilters.errorResponseHandler()
+                .filter(CLIENT_REQUEST, req -> Mono.just(errorResponse));
+
+        // then
+        assertThatThrownBy(result::block)
+                .isInstanceOf(WebClientResponseException.class);
+    }
+
+    @Test
+    void givenNotFoundResponse_whenHandleNotFoundResponseApplied_thenReturnsSyntheticResponse() {
+        // given
+        ClientResponse notFoundResponse = ClientResponse.create(HttpStatus.NOT_FOUND)
+                .header("X-Test", "value")
+                .build();
+
+        // when
+        Mono<ClientResponse> result = WebClientFilters.handleNotFoundResponse()
+                .filter(CLIENT_REQUEST, req -> Mono.just(notFoundResponse));
+        ClientResponse syntheticResponse = result.block();
+
+        // then
+        assertThat(syntheticResponse).isNotNull();
+        assertThat(syntheticResponse.statusCode()).isEqualTo(HttpStatus.OK);
+    }
+
+    @Test
+    void givenNonNotFoundResponse_whenHandleNotFoundResponseApplied_thenResponseIsUnchanged() {
+        // given
+        ClientResponse errorResponse = ClientResponse.create(HttpStatus.BAD_REQUEST).build();
+
+        // when
+        Mono<ClientResponse> result = WebClientFilters.handleNotFoundResponse()
+                .filter(CLIENT_REQUEST, req -> Mono.just(errorResponse));
+        ClientResponse response = result.block();
+
+        // then
+        assertThat(response).isNotNull();
+        assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
+    }
+}
diff --git a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/service/MaatCourtDataServiceTest.java b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/service/MaatCourtDataServiceTest.java
index feff9d8f..b231a2e8 100644
--- a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/service/MaatCourtDataServiceTest.java
+++ b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/service/MaatCourtDataServiceTest.java
@@ -4,16 +4,13 @@
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
-import org.mockito.Spy;
 import org.mockito.junit.jupiter.MockitoExtension;
 import uk.gov.justice.laa.crime.common.model.meansassessment.maatapi.MaatApiAssessmentRequest;
 import uk.gov.justice.laa.crime.common.model.meansassessment.maatapi.MaatApiAssessmentResponse;
-import uk.gov.justice.laa.crime.commons.client.RestAPIClient;
 import uk.gov.justice.laa.crime.enums.RequestType;
-import uk.gov.justice.laa.crime.meansassessment.config.MaatApiConfiguration;
+import uk.gov.justice.laa.crime.meansassessment.client.MaatCourtDataApiClient;
 import uk.gov.justice.laa.crime.meansassessment.data.builder.TestModelDataBuilder;
 import uk.gov.justice.laa.crime.meansassessment.dto.maatcourtdata.*;
-import uk.gov.justice.laa.crime.meansassessment.util.MockMaatApiConfiguration;
 
 import java.util.Map;
 
@@ -26,19 +23,16 @@
 class MaatCourtDataServiceTest {
 
     @Mock
-    RestAPIClient maatAPIClient;
+    MaatCourtDataApiClient maatAPIClient;
 
     @InjectMocks
     private MaatCourtDataService maatCourtDataService;
 
-    @Spy
-    private MaatApiConfiguration maatApiConfiguration = MockMaatApiConfiguration.getConfiguration(1000);
-
     @Test
     void givenCreateRequest_whenPersistMeansAssessmentIsInvoked_thenPostRequestIsSentToCourtDataApi() {
         MaatApiAssessmentResponse expected = new MaatApiAssessmentResponse().withId(1234);
 
-        when(maatAPIClient.post(any(MaatApiAssessmentRequest.class), any(), anyString(), anyMap()))
+        when(maatAPIClient.createFinancialAssessment(any(MaatApiAssessmentRequest.class)))
                 .thenReturn(expected);
 
         MaatApiAssessmentResponse response = maatCourtDataService.persistMeansAssessment(
@@ -49,7 +43,7 @@ void givenCreateRequest_whenPersistMeansAssessmentIsInvoked_thenPostRequestIsSen
     @Test
     void givenUpdateRequest_whenPersistMeansAssessmentIsInvoked_thenPutRequestIsSentToCourtDataApi() {
         MaatApiAssessmentResponse expected = new MaatApiAssessmentResponse().withId(5678);
-        when(maatAPIClient.put(any(MaatApiAssessmentRequest.class), any(), anyString(), anyMap()))
+        when(maatAPIClient.updateFinancialAssessment(any(MaatApiAssessmentRequest.class)))
                 .thenReturn(expected);
 
         MaatApiAssessmentResponse response = maatCourtDataService.persistMeansAssessment(
@@ -60,7 +54,7 @@ void givenUpdateRequest_whenPersistMeansAssessmentIsInvoked_thenPutRequestIsSent
     @Test
     void givenRepId_whenGetPassportAssessmentFromRepIdIsInvoked_thenResponseIsReturned() {
         PassportAssessmentDTO expected = new PassportAssessmentDTO();
-        when(maatAPIClient.get(any(), anyString(), any()))
+        when(maatAPIClient.getPassportAssessmentFromRepId(anyInt()))
                 .thenReturn(expected);
 
         PassportAssessmentDTO response =
@@ -72,7 +66,7 @@ void givenRepId_whenGetPassportAssessmentFromRepIdIsInvoked_thenResponseIsReturn
     @Test
     void givenRepId_whenGetHardshipReviewFromRepIdIsInvoked_thenResponseIsReturned() {
         HardshipReviewDTO expected = new HardshipReviewDTO();
-        when(maatAPIClient.get(any(), anyString(), any()))
+        when(maatAPIClient.getHardshipReviewFromRepId(anyInt()))
                 .thenReturn(expected);
 
         HardshipReviewDTO response =
@@ -84,7 +78,7 @@ void givenRepId_whenGetHardshipReviewFromRepIdIsInvoked_thenResponseIsReturned()
     @Test
     void givenRepId_whenGetIojAppealFromRepIdIsInvoked_thenResponseIsReturned() {
         IOJAppealDTO expected = new IOJAppealDTO();
-        when(maatAPIClient.get(any(), anyString(), any()))
+        when(maatAPIClient.getIOJAppealFromRepId(anyInt()))
                 .thenReturn(expected);
 
         IOJAppealDTO response =
@@ -96,7 +90,7 @@ void givenRepId_whenGetIojAppealFromRepIdIsInvoked_thenResponseIsReturned() {
     @Test
     void givenRepId_whenGetFinancialAssessmentIsInvoked_thenResponseIsReturned() {
         FinancialAssessmentDTO expected = new FinancialAssessmentDTO();
-        when(maatAPIClient.get(any(), anyString(), any()))
+        when(maatAPIClient.getFinancialAssessment(anyInt()))
                 .thenReturn(expected);
 
         FinancialAssessmentDTO response =
@@ -108,7 +102,7 @@ void givenRepId_whenGetFinancialAssessmentIsInvoked_thenResponseIsReturned() {
     @Test
     void givenRepId_whenGetRepOrderIsInvoked_thenResponseIsReturned() {
         RepOrderDTO expected = new RepOrderDTO();
-        when(maatAPIClient.get(any(), anyString(), any()))
+        when(maatAPIClient.getRepOrder(anyInt()))
                 .thenReturn(expected);
 
         RepOrderDTO response =
@@ -120,18 +114,13 @@ void givenRepId_whenGetRepOrderIsInvoked_thenResponseIsReturned() {
     @Test
     void givenDateCompletionRequest_whenUpdateCompletionDateIsInvoked_thenResponseIsReturned() {
         maatCourtDataService.updateCompletionDate(DateCompletionRequestDTO.builder().build());
-        verify(maatAPIClient).post(
-                any(DateCompletionRequestDTO.class),
-                any(),
-                anyString(),
-                anyMap()
-        );
+        verify(maatAPIClient).updateCompletionDate(any(DateCompletionRequestDTO.class));
     }
 
     @Test
     void givenFinancialAssessmentId_whenUpdateFinancialAssessmentIsInvoked_thenPatchRequestIsSent() {
         maatCourtDataService.rollbackFinancialAssessment(TestModelDataBuilder.TEST_REP_ID, Map.of());
-        verify(maatAPIClient).patch(any(), any(), anyString(), anyMap(), any());
+        verify(maatAPIClient).patchFinancialAssessment(anyMap(), anyInt());
     }
 
 }
\ No newline at end of file
diff --git a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/tracing/TraceIdHandlerTest.java b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/tracing/TraceIdHandlerTest.java
new file mode 100644
index 00000000..9b5cbdf1
--- /dev/null
+++ b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/tracing/TraceIdHandlerTest.java
@@ -0,0 +1,53 @@
+package uk.gov.justice.laa.crime.meansassessment.tracing;
+
+import io.micrometer.tracing.CurrentTraceContext;
+import io.micrometer.tracing.TraceContext;
+import io.micrometer.tracing.Tracer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.when;
+
+
+@ExtendWith(MockitoExtension.class)
+class TraceIdHandlerTest {
+
+    @InjectMocks
+    private TraceIdHandler traceIdHandler;
+
+    @Mock
+    private Tracer tracer;
+
+    @Mock
+    private CurrentTraceContext currentTraceContext;
+
+    @Mock
+    private TraceContext traceContext;
+
+    @Test
+    void whenCurrentTraceContextIsNull_thenTraceIdIsBlank() {
+        when(tracer.currentTraceContext()).thenReturn(null);
+        assertTrue(traceIdHandler.getTraceId().isBlank());
+    }
+
+    @Test
+    void whenTraceContextIsNull_thenTraceIdIsBlank() {
+        when(tracer.currentTraceContext()).thenReturn(currentTraceContext);
+        when(currentTraceContext.context()).thenReturn(null);
+        assertTrue(traceIdHandler.getTraceId().isBlank());
+    }
+
+    @Test
+    void whenTraceContextIsNotNull_thenReturnValidTraceId() {
+        String traceId = "mock-trace-id";
+        when(tracer.currentTraceContext()).thenReturn(currentTraceContext);
+        when(currentTraceContext.context()).thenReturn(traceContext);
+        when(traceContext.traceId()).thenReturn(traceId);
+        assertEquals(traceId, traceIdHandler.getTraceId());
+    }
+}
diff --git a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/util/MockMaatApiConfiguration.java b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/util/MockMaatApiConfiguration.java
deleted file mode 100644
index 994837a8..00000000
--- a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/util/MockMaatApiConfiguration.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package uk.gov.justice.laa.crime.meansassessment.util;
-
-import uk.gov.justice.laa.crime.meansassessment.config.MaatApiConfiguration;
-
-public class MockMaatApiConfiguration {
-
-    public static final String MOCK_BASE_URL = "http://localhost:9999";
-    public static final String MOCK_ASSESSMENT_BASE_URL = MOCK_BASE_URL.concat("/financial-assessments");
-
-    public static MaatApiConfiguration getConfiguration(int port) {
-
-        MaatApiConfiguration configuration = new MaatApiConfiguration();
-
-        MaatApiConfiguration.IOJAppealEndpoints iojEndpoints =
-                new MaatApiConfiguration.IOJAppealEndpoints(MOCK_BASE_URL + "/ioj-appeal/{repId}");
-        MaatApiConfiguration.HardshipReviewEndpoints hardshipEndpoints =
-                new MaatApiConfiguration.HardshipReviewEndpoints(MOCK_BASE_URL + "/hardship/{repId}");
-        MaatApiConfiguration.PassportAssessmentEndpoints passportEndpoints =
-                new MaatApiConfiguration.PassportAssessmentEndpoints(MOCK_BASE_URL + "/passport-assessments/{repId}");
-        MaatApiConfiguration.FinancialAssessmentEndpoints financialAssessmentEndpoints =
-                new MaatApiConfiguration.FinancialAssessmentEndpoints(
-                        MOCK_ASSESSMENT_BASE_URL + "/{financialAssessmentId}",
-                        MOCK_ASSESSMENT_BASE_URL,
-                        MOCK_ASSESSMENT_BASE_URL + "/{financialAssessmentId}",
-                        MOCK_ASSESSMENT_BASE_URL + "/rollback/{financialAssessmentId}"
-                );
-        MaatApiConfiguration.RepOrderEndpoints repOrderEndpoints =
-                new MaatApiConfiguration.RepOrderEndpoints(
-                        MOCK_BASE_URL + "/rep-orders",
-                        MOCK_BASE_URL + "/rep-orders/update-date-completed"
-                );
-        MaatApiConfiguration.ValidationEndpoints validationEndpoints = new MaatApiConfiguration.ValidationEndpoints(
-                MOCK_BASE_URL + "/authorization/users/{username}/actions/{action}",
-                MOCK_BASE_URL + "/authorization/users/{username}/work-reasons/{nworCode}",
-                MOCK_BASE_URL + "/authorization/users/{username}/reservations/{reservationId}/sessions/{sessionId}",
-                MOCK_BASE_URL + "/financial-assessments/check-outstanding/{repId}"
-        );
-        configuration.setBaseUrl(
-                String.format("http://localhost:%s", port)
-        );
-        configuration.setOAuthEnabled(false);
-        configuration.setPostProcessingUrl("/post-processing/{repId}");
-        configuration.setIojAppealEndpoints(iojEndpoints);
-        configuration.setHardshipReviewEndpoints(hardshipEndpoints);
-        configuration.setPassportAssessmentEndpoints(passportEndpoints);
-        configuration.setRepOrderEndpoints(repOrderEndpoints);
-        configuration.setValidationEndpoints(validationEndpoints);
-        configuration.setFinancialAssessmentEndpoints(financialAssessmentEndpoints);
-
-        return configuration;
-    }
-}
diff --git a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/validation/service/MeansAssessmentValidationServiceTest.java b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/validation/service/MeansAssessmentValidationServiceTest.java
index a88d4d18..ba0fc70e 100644
--- a/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/validation/service/MeansAssessmentValidationServiceTest.java
+++ b/crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/validation/service/MeansAssessmentValidationServiceTest.java
@@ -5,17 +5,13 @@
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
-import org.mockito.Spy;
 import org.mockito.junit.jupiter.MockitoExtension;
-import org.springframework.core.ParameterizedTypeReference;
-import uk.gov.justice.laa.crime.commons.client.RestAPIClient;
 import uk.gov.justice.laa.crime.enums.NewWorkReason;
-import uk.gov.justice.laa.crime.meansassessment.config.MaatApiConfiguration;
+import uk.gov.justice.laa.crime.meansassessment.client.MaatCourtDataApiClient;
 import uk.gov.justice.laa.crime.meansassessment.data.builder.TestModelDataBuilder;
 import uk.gov.justice.laa.crime.meansassessment.dto.AuthorizationResponseDTO;
 import uk.gov.justice.laa.crime.meansassessment.dto.MeansAssessmentRequestDTO;
 import uk.gov.justice.laa.crime.meansassessment.dto.OutstandingAssessmentResultDTO;
-import uk.gov.justice.laa.crime.meansassessment.util.MockMaatApiConfiguration;
 
 import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
 import static org.mockito.Mockito.*;
@@ -32,13 +28,11 @@ class MeansAssessmentValidationServiceTest {
     private static final OutstandingAssessmentResultDTO IS_OUTSTANDING_ASSESSMENT =
             OutstandingAssessmentResultDTO.builder().outstandingAssessments(true).build();
     @Mock
-    RestAPIClient maatAPIClient;
+    MaatCourtDataApiClient maatAPIClient;
     private MeansAssessmentRequestDTO requestDTO;
 
     @InjectMocks
     private MeansAssessmentValidationService meansAssessmentValidationService;
-    @Spy
-    private MaatApiConfiguration maatApiConfiguration = MockMaatApiConfiguration.getConfiguration(1000);
 
     @BeforeEach
     void setup() {
@@ -53,17 +47,14 @@ void whenGetUserIdFromRequestIsCalled_thenUserIdIsReturned() {
 
     @Test
     void givenInvalidNewWorkReason_whenIsNewWorkReasonValidIsInvoked_thenFalseIsReturned() {
-        when(maatAPIClient.get(
-                eq(new ParameterizedTypeReference<AuthorizationResponseDTO>() {
-                }), anyString(), anyString(), anyString()))
+        when(maatAPIClient.getNewWorkReason( anyString(), anyString()))
                 .thenReturn(FALSE_AUTH_RESPONSE);
         assertThat(meansAssessmentValidationService.isNewWorkReasonValid(requestDTO)).isFalse();
     }
 
     @Test
     void givenValidNewWorkReason_whenIsNewWorkReasonValidIsInvoked_thenTrueIsReturned() {
-        when(maatAPIClient.get(eq(new ParameterizedTypeReference<AuthorizationResponseDTO>() {
-        }), anyString(), anyString(), anyString()))
+        when(maatAPIClient.getNewWorkReason( anyString(), anyString()))
                 .thenReturn(TRUE_AUTH_RESPONSE);
         assertThat(meansAssessmentValidationService.isNewWorkReasonValid(requestDTO)).isTrue();
     }
@@ -87,32 +78,28 @@ void givenBlankAction_whenIsRoleActionValidIsInvoked_thenFalseIsReturned() {
 
     @Test
     void givenInvalidRoleAction_whenIsRoleActionValidIsInvoked_thenFalseIsReturned() {
-        when(maatAPIClient.get(eq(new ParameterizedTypeReference<AuthorizationResponseDTO>() {
-        }), anyString(), anyString(), anyString()))
+        when(maatAPIClient.getUserRoleAction(anyString(), anyString()))
                 .thenReturn(FALSE_AUTH_RESPONSE);
         assertThat(meansAssessmentValidationService.isRoleActionValid(requestDTO, "FMA")).isFalse();
     }
 
     @Test
     void givenValidRoleAction_whenIsRoleActionValidIsInvoked_thenTrueIsReturned() {
-        when(maatAPIClient.get(eq(new ParameterizedTypeReference<AuthorizationResponseDTO>() {
-        }), anyString(), anyString(), anyString()))
+        when(maatAPIClient.getUserRoleAction(anyString(), anyString()))
                 .thenReturn(TRUE_AUTH_RESPONSE);
         assertThat(meansAssessmentValidationService.isRoleActionValid(requestDTO, "FMA")).isTrue();
     }
 
     @Test
     void givenValidReservation_whenIsRepOrderReserved_thenTrueIsReturned() {
-        when(maatAPIClient.get(eq(new ParameterizedTypeReference<AuthorizationResponseDTO>() {
-        }), anyString(), anyString(), anyInt(), anyString()))
+        when(maatAPIClient.getReservationDetail(anyString(), anyInt(), anyString()))
                 .thenReturn(TRUE_AUTH_RESPONSE);
         assertThat(meansAssessmentValidationService.isRepOrderReserved(requestDTO)).isTrue();
     }
 
     @Test
     void givenInvalidReservation_whenIsRepOrderReserved_thenFalseIsReturned() {
-        when(maatAPIClient.get(eq(new ParameterizedTypeReference<AuthorizationResponseDTO>() {
-        }), anyString(), anyString(), anyInt(), anyString()))
+        when(maatAPIClient.getReservationDetail(anyString(), anyInt(), anyString()))
                 .thenReturn(FALSE_AUTH_RESPONSE);
         assertThat(meansAssessmentValidationService.isRepOrderReserved(requestDTO)).isFalse();
     }
@@ -137,16 +124,14 @@ void givenNullRepIdInRequest_whenIsRepOrderReserved_thenFalseIsReturned() {
 
     @Test
     void givenAnOutstandingAssessment_whenIsOutstandingAssessmentIsInvoked_thenTrueIsReturned() {
-        when(maatAPIClient.get(eq(new ParameterizedTypeReference<OutstandingAssessmentResultDTO>() {
-        }), anyString(), any()))
+        when(maatAPIClient.getOutstandingAssessment(anyInt()))
                 .thenReturn(IS_OUTSTANDING_ASSESSMENT);
         assertThat(meansAssessmentValidationService.isOutstandingAssessment(requestDTO)).isTrue();
     }
 
     @Test
     void givenNoOutstandingAssessments_whenIsOutstandingAssessmentIsInvoked_thenFalseIsReturned() {
-        when(maatAPIClient.get(eq(new ParameterizedTypeReference<OutstandingAssessmentResultDTO>() {
-        }), anyString(), any()))
+        when(maatAPIClient.getOutstandingAssessment(anyInt()))
                 .thenReturn(NO_OUTSTANDING_ASSESSMENT);
         assertThat(meansAssessmentValidationService.isOutstandingAssessment(requestDTO)).isFalse();
     }
diff --git a/crime-means-assessment/src/test/resources/application.yaml b/crime-means-assessment/src/test/resources/application.yaml
index ed7fa72b..3861c2c9 100644
--- a/crime-means-assessment/src/test/resources/application.yaml
+++ b/crime-means-assessment/src/test/resources/application.yaml
@@ -51,31 +51,10 @@ springdoc:
 swagger-ui:
   path: /open-api/docs.html
 
-maatApi:
-  oAuthEnabled: false
-  baseUrl: http://localhost:9999
-  assessments-domain: ${maatApi.baseUrl}/api/internal/v1/assessment
-  post-processing-url: ${maatApi.assessments-domain}/post-processing/{repId}
-
-  validation-endpoints:
-    role-action-url: ${maatApi.assessments-domain}/role-action-url
-    new-work-reason-url: ${maatApi.assessments-domain}/new-work-reason-url
-    reservations-url: ${maatApi.assessments-domain}/reservation-url
-    outstanding-assessments-url: ${maatApi.assessments-domain}/outstanding-assessments-url
-  financial-assessment-endpoints:
-    create-url: ${maatApi.assessments-domain}/create-url
-    search-url: ${maatApi.assessments-domain}/search-url
-    update-url: ${maatApi.assessments-domain}/update-url
-    rollback-url: ${maatApi.assessments-domain}/rollback-url
-  passport-assessment-endpoints:
-    find-url: ${maatApi.assessments-domain}/passport-assessments/repId/{repId}
-  hardship-review-endpoints:
-    find-url: ${maatApi.assessments-domain}/hardship/repId/{repId}
-  ioj-appeal-endpoints:
-    find-url: ${maatApi.assessments-domain}/ioj-appeal/repId/{repId}
-  rep-order-endpoints:
-    find-url: ${maatApi.assessments-domain}/rep-orders/{repId}
-    date-completion-url: ${maatApi.assessments-domain}/rep-orders/update-date-completed
+services:
+  maat-api:
+    baseUrl: http://localhost:9999/api/internal/v1/assessment
+    registrationId: maat-api
 
 means-assessment:
   security:
@@ -83,10 +62,5 @@ means-assessment:
 
 version: 0.0.1
 
-retry-config:
-  max-retries: 2
-  min-back-off-period: 3
-  jitter-value: 0.5
-
 features:
   date-completion-enabled: false
diff --git a/helm_deploy/laa-crime-means-assessment/values-dev.yaml b/helm_deploy/laa-crime-means-assessment/values-dev.yaml
index 8d0b8486..f2b421e7 100644
--- a/helm_deploy/laa-crime-means-assessment/values-dev.yaml
+++ b/helm_deploy/laa-crime-means-assessment/values-dev.yaml
@@ -22,7 +22,7 @@ jwt:
   issuerUri: https://cognito-idp.eu-west-2.amazonaws.com/eu-west-2_Bo3WVvzAk
 
 maatApi:
-  baseUrl: https://maat-cd-api.dev.legalservices.gov.uk
+  baseUrl: https://maat-cd-api.dev.legalservices.gov.uk/api/internal/v1/assessment
   oauthUrl: https://maat-cd-api-dev.auth.eu-west-2.amazoncognito.com/oauth2/token
 
 serviceAccount:
diff --git a/helm_deploy/laa-crime-means-assessment/values-prod.yaml b/helm_deploy/laa-crime-means-assessment/values-prod.yaml
index ddee1c10..df1f405c 100644
--- a/helm_deploy/laa-crime-means-assessment/values-prod.yaml
+++ b/helm_deploy/laa-crime-means-assessment/values-prod.yaml
@@ -22,7 +22,7 @@ jwt:
   issuerUri: https://cognito-idp.eu-west-2.amazonaws.com/eu-west-2_Bo3WVvzAk
 
 maatApi:
-  baseUrl: https://maat-cd-api.legalservices.gov.uk
+  baseUrl: https://maat-cd-api.legalservices.gov.uk/api/internal/v1/assessment
   oauthUrl: https://maat-cd-api-prd.auth.eu-west-2.amazoncognito.com/oauth2/token
 
 serviceAccount:
diff --git a/helm_deploy/laa-crime-means-assessment/values-test.yaml b/helm_deploy/laa-crime-means-assessment/values-test.yaml
index 8d43b2ba..8405ac99 100644
--- a/helm_deploy/laa-crime-means-assessment/values-test.yaml
+++ b/helm_deploy/laa-crime-means-assessment/values-test.yaml
@@ -1 +1 @@
-# Default values for laa-crime-means-assessment.# This is a YAML-formatted file.# Declare variables to be passed into your templates.replicaCount: 2image:  repository: 754256621582.dkr.ecr.eu-west-2.amazonaws.com/laa-crime-apps-team/laa-crime-means-assessment-dev-ecr  pullPolicy: IfNotPresent  # Overrides the image tag whose default is the chart appVersion.  # tag: latestsentry:  sampleRate: 0.05aws_region: eu-west-2java:  host_env: testjwt:  issuerUri: https://cognito-idp.eu-west-2.amazonaws.com/eu-west-2_Bo3WVvzAkmaatApi:  baseUrl: https://maat-cd-api.tst.legalservices.gov.uk  oauthUrl: https://maat-cd-api-tst.auth.eu-west-2.amazoncognito.com/oauth2/tokenserviceAccount:  # Specifies whether a service account should be created  create: false  # Annotations to add to the service account  annotations: {}  # The name of the service account to use.  # If not set and create is true, a name is generated using the fullname template  name: "laa-crime-means-assessment"service:  type: ClusterIP  port: 80  targetPort: 8080ingress:  enabled: true  environmentName: laa-crime-means-assessment-test  annotations:    external-dns.alpha.kubernetes.io/aws-weight: "100"    nginx.ingress.kubernetes.io/affinity: "cookie"    nginx.ingress.kubernetes.io/session-cookie-max-age: "300"  externalAnnotations: {}  hosts:    - host: laa-crime-means-assessment-test.apps.live.cloud-platform.service.justice.gov.uk      paths: ["/"]  tls: []  className: defaultautoscaling:  enabled: false  minReplicas: 1  maxReplicas: 100  targetCPUUtilizationPercentage: 80  targetMemoryUtilizationPercentage: 80actuator:  metrics:    enabled: true    scrapeInterval: 15s    path: /actuator/prometheus  health:    path: /actuator/health  port: 8096  liveness:    initialDelaySeconds: 45    periodSeconds: 10    timeoutSeconds: 10    failureThreshold: 5  readiness:    initialDelaySeconds: 45    periodSeconds: 10    timeoutSeconds: 10    failureThreshold: 5scheduledDowntime:  enabled: true  # Start at 6am UTC Monday-Friday  startup: '0 6 * * 1-5'  # Stop at 10pm UTC Monday-Friday  shutdown: '0 22 * * 1-5'  serviceAccountName: scheduled-downtime-serviceaccountsecurityContext:  allowPrivilegeEscalation: false  capabilities:    drop: ["ALL"]  runAsNonRoot: true  seccompProfile:    type: RuntimeDefaultlogging:  level: INFO
\ No newline at end of file
+# Default values for laa-crime-means-assessment.# This is a YAML-formatted file.# Declare variables to be passed into your templates.replicaCount: 2image:  repository: 754256621582.dkr.ecr.eu-west-2.amazonaws.com/laa-crime-apps-team/laa-crime-means-assessment-dev-ecr  pullPolicy: IfNotPresent  # Overrides the image tag whose default is the chart appVersion.  # tag: latestsentry:  sampleRate: 0.05aws_region: eu-west-2java:  host_env: testjwt:  issuerUri: https://cognito-idp.eu-west-2.amazonaws.com/eu-west-2_Bo3WVvzAkmaatApi:  baseUrl: https://maat-cd-api.tst.legalservices.gov.uk/api/internal/v1/assessment  oauthUrl: https://maat-cd-api-tst.auth.eu-west-2.amazoncognito.com/oauth2/tokenserviceAccount:  # Specifies whether a service account should be created  create: false  # Annotations to add to the service account  annotations: {}  # The name of the service account to use.  # If not set and create is true, a name is generated using the fullname template  name: "laa-crime-means-assessment"service:  type: ClusterIP  port: 80  targetPort: 8080ingress:  enabled: true  environmentName: laa-crime-means-assessment-test  annotations:    external-dns.alpha.kubernetes.io/aws-weight: "100"    nginx.ingress.kubernetes.io/affinity: "cookie"    nginx.ingress.kubernetes.io/session-cookie-max-age: "300"  externalAnnotations: {}  hosts:    - host: laa-crime-means-assessment-test.apps.live.cloud-platform.service.justice.gov.uk      paths: ["/"]  tls: []  className: defaultautoscaling:  enabled: false  minReplicas: 1  maxReplicas: 100  targetCPUUtilizationPercentage: 80  targetMemoryUtilizationPercentage: 80actuator:  metrics:    enabled: true    scrapeInterval: 15s    path: /actuator/prometheus  health:    path: /actuator/health  port: 8096  liveness:    initialDelaySeconds: 45    periodSeconds: 10    timeoutSeconds: 10    failureThreshold: 5  readiness:    initialDelaySeconds: 45    periodSeconds: 10    timeoutSeconds: 10    failureThreshold: 5scheduledDowntime:  enabled: true  # Start at 6am UTC Monday-Friday  startup: '0 6 * * 1-5'  # Stop at 10pm UTC Monday-Friday  shutdown: '0 22 * * 1-5'  serviceAccountName: scheduled-downtime-serviceaccountsecurityContext:  allowPrivilegeEscalation: false  capabilities:    drop: ["ALL"]  runAsNonRoot: true  seccompProfile:    type: RuntimeDefaultlogging:  level: INFO
\ No newline at end of file
diff --git a/helm_deploy/laa-crime-means-assessment/values-uat.yaml b/helm_deploy/laa-crime-means-assessment/values-uat.yaml
index fe737955..6c7f2c7f 100644
--- a/helm_deploy/laa-crime-means-assessment/values-uat.yaml
+++ b/helm_deploy/laa-crime-means-assessment/values-uat.yaml
@@ -22,7 +22,7 @@ jwt:
   issuerUri: https://cognito-idp.eu-west-2.amazonaws.com/eu-west-2_Bo3WVvzAk
 
 maatApi:
-  baseUrl: https://maat-cd-api.uat.legalservices.gov.uk
+  baseUrl: https://maat-cd-api.uat.legalservices.gov.uk/api/internal/v1/assessment
   oauthUrl: https://maat-cd-api-uat.auth.eu-west-2.amazoncognito.com/oauth2/token
 
 serviceAccount:


Reviews:
user: login: Mhowell494
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T16:20:04Z
user: login: loganathanMOJ
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-23T16:54:40Z
user: login: Mhowell494
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T11:27:18Z
user: login: Mhowell494
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:59:51Z
user: login: Mhowell494
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-24T14:59:57Z

Review comments:
diff_hunk: @@ -43,7 +45,7 @@ repositories {
 }
 
 dependencies {
-    implementation "uk.gov.justice.service.laa-crime:crime-commons-spring-boot-starter-rest-client:$versions.crimeCommonsRestClientVersion:all"
+    implementation("org.jetbrains:annotations:24.1.0")
path: crime-means-assessment/build.gradle
user: login: Mhowell494
body: I believe this was only ever used in the `RetryConfiguration` class, which is now redundant.
created_at: 2025-04-22T12:34:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23,6 +22,9 @@ def versions = [
         postgresqlVersion            : "42.7.2",
         tomcatVersion                : "10.1.34",
path: crime-means-assessment/build.gradle
user: login: Mhowell494
body: I think you're going to run into issues deploying this as there is a security vulnerability with this dependency. I'd suggest bumping up to version `10.1.39`
created_at: 2025-04-22T12:35:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -58,6 +60,13 @@ dependencies {
     implementation "io.micrometer:micrometer-registry-prometheus"
     implementation "org.liquibase:liquibase-core:$versions.liquibase"
     implementation "org.springdoc:springdoc-openapi-starter-webmvc-ui:$versions.springdocVersion"
+    implementation "io.github.resilience4j:resilience4j-reactor:$versions.resilience4jVersion"
+    implementation "io.github.resilience4j:resilience4j-spring-boot3:$versions.resilience4jVersion"
+    implementation "org.springframework.boot:spring-boot-starter-actuator"
+    implementation "org.springframework.boot:spring-boot-starter-webflux"
+    implementation "org.springframework.boot:spring-boot-starter-web"
+    implementation "org.springframework.boot:spring-boot-starter-oauth2-client"
+    implementation "io.micrometer:micrometer-tracing"
path: crime-means-assessment/build.gradle
user: login: Mhowell494
body: There's no logic ordering right now, can we properly group the dependencies? 
created_at: 2025-04-22T12:37:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+package uk.gov.justice.laa.crime.meansassessment.client;
+
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.service.annotation.*;
+import uk.gov.justice.laa.crime.common.model.meansassessment.maatapi.MaatApiAssessmentRequest;
+import uk.gov.justice.laa.crime.common.model.meansassessment.maatapi.MaatApiAssessmentResponse;
+import uk.gov.justice.laa.crime.meansassessment.dto.AuthorizationResponseDTO;
+import uk.gov.justice.laa.crime.meansassessment.dto.OutstandingAssessmentResultDTO;
+import uk.gov.justice.laa.crime.meansassessment.dto.maatcourtdata.*;
+
+import java.util.Map;
+
+@HttpExchange()
+public interface MaatCourtDataApiClient {
+
+    @PostExchange("/financial-assessments")
+    MaatApiAssessmentResponse create(@RequestBody MaatApiAssessmentRequest assessment);
+
+    @PutExchange("/financial-assessments")
+    MaatApiAssessmentResponse update(@RequestBody MaatApiAssessmentRequest assessment);
path: crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/client/MaatCourtDataApiClient.java
user: login: Mhowell494
body: Isn't clear what these methods doe unless you're looking at the interface, should be changed to `createFinancialAssessment` and `updateFinancialAssessment`
created_at: 2025-04-22T12:41:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1 +1 @@
-# Default values for laa-crime-means-assessment.# This is a YAML-formatted file.# Declare variables to be passed into your templates.replicaCount: 2image:  repository: 754256621582.dkr.ecr.eu-west-2.amazonaws.com/laa-crime-apps-team/laa-crime-means-assessment-dev-ecr  pullPolicy: IfNotPresent  # Overrides the image tag whose default is the chart appVersion.  # tag: latestsentry:  sampleRate: 0.05aws_region: eu-west-2java:  host_env: testjwt:  issuerUri: https://cognito-idp.eu-west-2.amazonaws.com/eu-west-2_Bo3WVvzAkmaatApi:  baseUrl: https://maat-cd-api.tst.legalservices.gov.uk  oauthUrl: https://maat-cd-api-tst.auth.eu-west-2.amazoncognito.com/oauth2/tokenserviceAccount:  # Specifies whether a service account should be created  create: false  # Annotations to add to the service account  annotations: {}  # The name of the service account to use.  # If not set and create is true, a name is generated using the fullname template  name: "laa-crime-means-assessment"service:  type: ClusterIP  port: 80  targetPort: 8080ingress:  enabled: true  environmentName: laa-crime-means-assessment-test  annotations:    external-dns.alpha.kubernetes.io/aws-weight: "100"    nginx.ingress.kubernetes.io/affinity: "cookie"    nginx.ingress.kubernetes.io/session-cookie-max-age: "300"  externalAnnotations: {}  hosts:    - host: laa-crime-means-assessment-test.apps.live.cloud-platform.service.justice.gov.uk      paths: ["/"]  tls: []  className: defaultautoscaling:  enabled: false  minReplicas: 1  maxReplicas: 100  targetCPUUtilizationPercentage: 80  targetMemoryUtilizationPercentage: 80actuator:  metrics:    enabled: true    scrapeInterval: 15s    path: /actuator/prometheus  health:    path: /actuator/health  port: 8096  liveness:    initialDelaySeconds: 45    periodSeconds: 10    timeoutSeconds: 10    failureThreshold: 5  readiness:    initialDelaySeconds: 45    periodSeconds: 10    timeoutSeconds: 10    failureThreshold: 5scheduledDowntime:  enabled: true  # Start at 6am UTC Monday-Friday  startup: '0 6 * * 1-5'  # Stop at 10pm UTC Monday-Friday  shutdown: '0 22 * * 1-5'  serviceAccountName: scheduled-downtime-serviceaccountsecurityContext:  allowPrivilegeEscalation: false  capabilities:    drop: ["ALL"]  runAsNonRoot: true  seccompProfile:    type: RuntimeDefaultlogging:  level: INFO
\ No newline at end of file
+# Default values for laa-crime-means-assessment.# This is a YAML-formatted file.# Declare variables to be passed into your templates.replicaCount: 2image:  repository: 754256621582.dkr.ecr.eu-west-2.amazonaws.com/laa-crime-apps-team/laa-crime-means-assessment-dev-ecr  pullPolicy: IfNotPresent  # Overrides the image tag whose default is the chart appVersion.  # tag: latestsentry:  sampleRate: 0.05aws_region: eu-west-2java:  host_env: testjwt:  issuerUri: https://cognito-idp.eu-west-2.amazonaws.com/eu-west-2_Bo3WVvzAkmaatApi:  baseUrl: https://maat-cd-api.tst.legalservices.gov.uk/api/internal/v1/assessment  oauthUrl: https://maat-cd-api-tst.auth.eu-west-2.amazoncognito.com/oauth2/tokenserviceAccount:  # Specifies whether a service account should be created  create: false  # Annotations to add to the service account  annotations: {}  # The name of the service account to use.  # If not set and create is true, a name is generated using the fullname template  name: "laa-crime-means-assessment"service:  type: ClusterIP  port: 80  targetPort: 8080ingress:  enabled: true  environmentName: laa-crime-means-assessment-test  annotations:    external-dns.alpha.kubernetes.io/aws-weight: "100"    nginx.ingress.kubernetes.io/affinity: "cookie"    nginx.ingress.kubernetes.io/session-cookie-max-age: "300"  externalAnnotations: {}  hosts:    - host: laa-crime-means-assessment-test.apps.live.cloud-platform.service.justice.gov.uk      paths: ["/"]  tls: []  className: defaultautoscaling:  enabled: false  minReplicas: 1  maxReplicas: 100  targetCPUUtilizationPercentage: 80  targetMemoryUtilizationPercentage: 80actuator:  metrics:    enabled: true    scrapeInterval: 15s    path: /actuator/prometheus  health:    path: /actuator/health  port: 8096  liveness:    initialDelaySeconds: 45    periodSeconds: 10    timeoutSeconds: 10    failureThreshold: 5  readiness:    initialDelaySeconds: 45    periodSeconds: 10    timeoutSeconds: 10    failureThreshold: 5scheduledDowntime:  enabled: true  # Start at 6am UTC Monday-Friday  startup: '0 6 * * 1-5'  # Stop at 10pm UTC Monday-Friday  shutdown: '0 22 * * 1-5'  serviceAccountName: scheduled-downtime-serviceaccountsecurityContext:  allowPrivilegeEscalation: false  capabilities:    drop: ["ALL"]  runAsNonRoot: true  seccompProfile:    type: RuntimeDefaultlogging:  level: INFO
path: helm_deploy/laa-crime-means-assessment/values-test.yaml
user: login: Mhowell494
body: It looks like this file was saved with Windows-style line endings (`CRLF`), which is causing the whole file to show as changed in the diff. Could you configure your editor or Git to use Unix-style (`LF`) line endings and recommit? That should clean up the diff so we only see the actual change to `maatApi.baseUrl`.
created_at: 2025-04-22T13:38:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,107 @@
+package uk.gov.justice.laa.crime.meansassessment.config;
+
+import io.github.resilience4j.retry.RetryRegistry;
+import io.netty.resolver.DefaultAddressResolverGroup;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.client.reactive.ReactorClientHttpConnector;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
+import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.support.WebClientAdapter;
+import org.springframework.web.service.invoker.HttpServiceProxyFactory;
+import reactor.netty.http.client.HttpClient;
+import reactor.netty.resources.ConnectionProvider;
+import uk.gov.justice.laa.crime.meansassessment.client.MaatCourtDataApiClient;
+import uk.gov.justice.laa.crime.meansassessment.filter.Resilience4jRetryFilter;
+import uk.gov.justice.laa.crime.meansassessment.filter.WebClientFilters;
+
+import java.time.Duration;
+import java.util.List;
+
+@Slf4j
+@Configuration
+@AllArgsConstructor
+public class WebClientsConfiguration {
+    public static final int MAX_IN_MEMORY_SIZE = 10485760;
+    public static final String COURT_DATA_API_WEB_CLIENT_NAME = "maatCourtDataWebClient";
+
+    @Bean
+    WebClientCustomizer webClientCustomizer() {
+        ConnectionProvider provider =
+                ConnectionProvider.builder("custom")
+                        .maxConnections(500)
+                        .maxIdleTime(Duration.ofSeconds(20))
+                        .maxLifeTime(Duration.ofSeconds(60))
+                        .evictInBackground(Duration.ofSeconds(120))
+                        .pendingAcquireTimeout(Duration.ofSeconds(60))
+                        .build();
+
+        return builder -> {
+            builder.clientConnector(
+                    new ReactorClientHttpConnector(
+                            HttpClient.create(provider)
+                                    .resolver(DefaultAddressResolverGroup.INSTANCE)
+                                    .compress(true)
+                                    .responseTimeout(Duration.ofSeconds(30))
+                    )
+            );
+            builder.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
+            builder.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
+            builder.codecs(configurer -> configurer
+                    .defaultCodecs()
+                    .maxInMemorySize(MAX_IN_MEMORY_SIZE)
+            );
+        };
+    }
+
+    @Bean(COURT_DATA_API_WEB_CLIENT_NAME)
+    WebClient maatCourtDataWebClient(WebClient.Builder webClientBuilder,
+                                     MaatApiConfiguration servicesConfiguration,
+                                     ClientRegistrationRepository clientRegistrations,
+                                     OAuth2AuthorizedClientRepository authorizedClients,
+                                     RetryRegistry retryRegistry) {
+
+        ServletOAuth2AuthorizedClientExchangeFilterFunction oauthFilter =
+                new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrations, authorizedClients);
+        oauthFilter.setDefaultClientRegistrationId(servicesConfiguration.getRegistrationId());
path: crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/config/WebClientsConfiguration.java
user: login: Mhowell494
body: Can we keep the names of the parameters consistent please? `servicesConfiguration` doesn't make sense here. Either update the name of this to `maatApiConfiguration`, or update the name of the class to `ServicesConfiguration` to match the other services (I actually think I prefer this option).
created_at: 2025-04-22T13:46:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+package uk.gov.justice.laa.crime.meansassessment.client;
+
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.service.annotation.*;
+import uk.gov.justice.laa.crime.common.model.meansassessment.maatapi.MaatApiAssessmentRequest;
+import uk.gov.justice.laa.crime.common.model.meansassessment.maatapi.MaatApiAssessmentResponse;
+import uk.gov.justice.laa.crime.meansassessment.dto.AuthorizationResponseDTO;
+import uk.gov.justice.laa.crime.meansassessment.dto.OutstandingAssessmentResultDTO;
+import uk.gov.justice.laa.crime.meansassessment.dto.maatcourtdata.*;
+
+import java.util.Map;
+
+@HttpExchange()
+public interface MaatCourtDataApiClient {
+
+    @PostExchange("/financial-assessments")
+    MaatApiAssessmentResponse create(@RequestBody MaatApiAssessmentRequest assessment);
+
+    @PutExchange("/financial-assessments")
+    MaatApiAssessmentResponse update(@RequestBody MaatApiAssessmentRequest assessment);
+
+    @PostExchange("/rep-orders/update-date-completed")
+    RepOrderDTO updateCompletionDate(@RequestBody DateCompletionRequestDTO dateCompletionRequestDTO);
+
+    @GetExchange("/passport-assessments/repId/{repId}")
+    PassportAssessmentDTO getPassportAssessmentFromRepId(@PathVariable Integer repId);
+
+    @GetExchange("/hardship/repId/{repId}")
+    HardshipReviewDTO getHardshipReviewFromRepId(@PathVariable Integer repId);
+
+    @GetExchange("/ioj-appeal/repId/{repId}")
+    IOJAppealDTO getIOJAppealFromRepId(@PathVariable Integer repId);
+
+    @GetExchange("/financial-assessments/{financialAssessmentId}")
+    FinancialAssessmentDTO getFinancialAssessment(@PathVariable Integer financialAssessmentId);
path: crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/client/MaatCourtDataApiClient.java
user: login: Mhowell494
body: Can we group this with the other financial assessment endpoints? Likewise for any other related methods.
created_at: 2025-04-22T15:40:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+package uk.gov.justice.laa.crime.meansassessment.client;
+
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.service.annotation.*;
+import uk.gov.justice.laa.crime.common.model.meansassessment.maatapi.MaatApiAssessmentRequest;
+import uk.gov.justice.laa.crime.common.model.meansassessment.maatapi.MaatApiAssessmentResponse;
+import uk.gov.justice.laa.crime.meansassessment.dto.AuthorizationResponseDTO;
+import uk.gov.justice.laa.crime.meansassessment.dto.OutstandingAssessmentResultDTO;
+import uk.gov.justice.laa.crime.meansassessment.dto.maatcourtdata.*;
+
+import java.util.Map;
+
+@HttpExchange()
+public interface MaatCourtDataApiClient {
+
+    @PostExchange("/financial-assessments")
+    MaatApiAssessmentResponse create(@RequestBody MaatApiAssessmentRequest assessment);
+
+    @PutExchange("/financial-assessments")
+    MaatApiAssessmentResponse update(@RequestBody MaatApiAssessmentRequest assessment);
+
+    @PostExchange("/rep-orders/update-date-completed")
+    RepOrderDTO updateCompletionDate(@RequestBody DateCompletionRequestDTO dateCompletionRequestDTO);
+
+    @GetExchange("/passport-assessments/repId/{repId}")
+    PassportAssessmentDTO getPassportAssessmentFromRepId(@PathVariable Integer repId);
+
+    @GetExchange("/hardship/repId/{repId}")
+    HardshipReviewDTO getHardshipReviewFromRepId(@PathVariable Integer repId);
+
+    @GetExchange("/ioj-appeal/repId/{repId}")
+    IOJAppealDTO getIOJAppealFromRepId(@PathVariable Integer repId);
+
+    @GetExchange("/financial-assessments/{financialAssessmentId}")
+    FinancialAssessmentDTO getFinancialAssessment(@PathVariable Integer financialAssessmentId);
+
+    @GetExchange("/rep-orders/{repId}")
+    RepOrderDTO getRepOrder(@PathVariable Integer repId);
+
+    @PatchExchange("/financial-assessments/rollback/{financialAssessmentId}")
+    PassportAssessmentDTO rollbackFinancialAssessment(@RequestBody Map<String, Object> updateFields,
path: crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/client/MaatCourtDataApiClient.java
user: login: Mhowell494
body: The return type is `PassportAssessmentDTO`? I think this should be renamed to `patchFinancialAssessment` and return `void`. Do we need additional test cases to enforce this?
created_at: 2025-04-22T15:47:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -52,7 +77,8 @@ public ResponseEntity<ErrorDTO> adviceServiceErrors(RuntimeException ex) {
         return getNewErrorResponseWith(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage(), traceIdHandler.getTraceId());
     }
 
-    private static ResponseEntity<ErrorDTO> getNewErrorResponseWith(HttpStatus status, String errorMessage, String traceId) {
+    private static ResponseEntity<ErrorDTO> getNewErrorResponseWith(HttpStatusCode status, String errorMessage, String traceId) {
         return new ResponseEntity<>(ErrorDTO.builder().traceId(traceId).code(status.toString()).message(errorMessage).build(), status);
     }
+
path: crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/exception/RestControllerAdviser.java
user: login: Mhowell494
body: Can remove the `handleApiClientError` method as it is now redundant.
created_at: 2025-04-22T15:53:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,23 +1,48 @@
 package uk.gov.justice.laa.crime.meansassessment.exception;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.HttpStatusCode;
 import org.springframework.http.ResponseEntity;
 import org.springframework.http.converter.HttpMessageNotReadableException;
 import org.springframework.web.bind.MethodArgumentNotValidException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseStatus;
 import org.springframework.web.bind.annotation.RestControllerAdvice;
-import uk.gov.justice.laa.crime.commons.tracing.TraceIdHandler;
-import uk.gov.justice.laa.crime.meansassessment.dto.ErrorDTO;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
+import org.springframework.web.reactive.function.client.WebClientResponseException;
+import uk.gov.justice.laa.crime.dto.ErrorDTO;
+import uk.gov.justice.laa.crime.meansassessment.tracing.TraceIdHandler;
+
+import java.io.IOException;
 
 @RestControllerAdvice
 @Slf4j
 @RequiredArgsConstructor
 public class RestControllerAdviser {
 
     private final TraceIdHandler traceIdHandler;
+    private final ObjectMapper mapper;
+
+    @ExceptionHandler(WebClientResponseException.class)
+    public ResponseEntity<ErrorDTO> onRuntimeException(WebClientResponseException exception) {
+        String errorMessage;
+        try {
+            ErrorDTO errorDTO = mapper.readValue(exception.getResponseBodyAsString(), ErrorDTO.class);
+            errorMessage = errorDTO.getMessage();
+        } catch (IOException ex) {
+            log.warn("Unable to read the ErrorDTO from WebClientResponseException", ex);
+            errorMessage = exception.getMessage();
+        }
+        return getNewErrorResponseWith(exception.getStatusCode(), errorMessage, traceIdHandler.getTraceId());
+    }
+
+    @ExceptionHandler(WebClientRequestException.class)
+    public ResponseEntity<ErrorDTO> onRuntimeException(WebClientRequestException exception) {
+        return getNewErrorResponseWith(HttpStatus.INTERNAL_SERVER_ERROR, exception.getMessage(), traceIdHandler.getTraceId());
+    }
 
     @ExceptionHandler(MethodArgumentNotValidException.class)
     @ResponseStatus(HttpStatus.BAD_REQUEST)
path: crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/exception/RestControllerAdviser.java
user: login: Mhowell494
body: As a bit of cleanup, can we remove the `@ResponseStatus` annotations please?
created_at: 2025-04-22T15:57:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -137,16 +124,14 @@ void givenNullRepIdInRequest_whenIsRepOrderReserved_thenFalseIsReturned() {
 
     @Test
     void givenAnOutstandingAssessment_whenIsOutstandingAssessmentIsInvoked_thenTrueIsReturned() {
-        when(maatAPIClient.get(eq(new ParameterizedTypeReference<OutstandingAssessmentResultDTO>() {
-        }), anyString(), any()))
+        when(maatAPIClient.getOutstandingAssessment( any()))
path: crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/validation/service/MeansAssessmentValidationServiceTest.java
user: login: Mhowell494
body: Can we get rid of the additional whitespace here please?
created_at: 2025-04-22T15:59:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -60,7 +55,7 @@ void givenUpdateRequest_whenPersistMeansAssessmentIsInvoked_thenPutRequestIsSent
     @Test
     void givenRepId_whenGetPassportAssessmentFromRepIdIsInvoked_thenResponseIsReturned() {
         PassportAssessmentDTO expected = new PassportAssessmentDTO();
-        when(maatAPIClient.get(any(), anyString(), any()))
+        when(maatAPIClient.getPassportAssessmentFromRepId( any()))
path: crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/service/MaatCourtDataServiceTest.java
user: login: Mhowell494
body: Could be good to improve some of these tests and specify the types rather than just using `any()`
created_at: 2025-04-22T16:09:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -121,12 +121,12 @@ private MockHttpServletRequestBuilder buildRequest(HttpMethod method, String con
     }
 
     private void stubForRoleActionAndReservationUrl() {
-        stubFor(get(urlEqualTo("/api/internal/v1/assessment/role-action-url")).willReturn(aResponse()
+        stubFor(get(urlPathMatching("/api/internal/v1/assessment/authorization/users/([a-zA-Z]*)/actions/([a-zA-Z_]*)")).willReturn(aResponse()
path: crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/MeansAssessmentIntegrationTest.java
user: login: Mhowell494
body: I'd stick these urls in a constant and keep using `urlEqualTo` where we're configuring the stubbing within a test.
created_at: 2025-04-22T16:19:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -43,7 +45,7 @@ repositories {
 }
 
 dependencies {
-    implementation "uk.gov.justice.service.laa-crime:crime-commons-spring-boot-starter-rest-client:$versions.crimeCommonsRestClientVersion:all"
+    implementation("org.jetbrains:annotations:24.1.0")
path: crime-means-assessment/build.gradle
user: login: loganathanMOJ
body: Which is used in MeansAssessmentService.java
created_at: 2025-04-23T16:54:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -32,14 +35,11 @@ public interface MaatCourtDataApiClient {
     @GetExchange("/ioj-appeal/repId/{repId}")
     IOJAppealDTO getIOJAppealFromRepId(@PathVariable Integer repId);
 
-    @GetExchange("/financial-assessments/{financialAssessmentId}")
-    FinancialAssessmentDTO getFinancialAssessment(@PathVariable Integer financialAssessmentId);
-
     @GetExchange("/rep-orders/{repId}")
     RepOrderDTO getRepOrder(@PathVariable Integer repId);
 
     @PatchExchange("/financial-assessments/rollback/{financialAssessmentId}")
-    PassportAssessmentDTO rollbackFinancialAssessment(@RequestBody Map<String, Object> updateFields,
+    void rollbackFinancialAssessment(@RequestBody Map<String, Object> updateFields,
path: crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/client/MaatCourtDataApiClient.java
user: login: Mhowell494
body: This still needs to be renamed to `patchFinancialAssessment`
created_at: 2025-04-24T11:21:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -55,7 +55,8 @@
 class MeansAssessmentIntegrationTest {
 
     private static final boolean IS_VALID = true;
-    private static final String ENDPOINT_URL = "/api/internal/v1/assessment/means";
+    private static final String API_VERSION = "/api/internal/v1/assessment";
+    private static final String ENDPOINT_URL = API_VERSION + "/means";
path: crime-means-assessment/src/test/java/uk/gov/justice/laa/crime/meansassessment/controller/MeansAssessmentIntegrationTest.java
user: login: Mhowell494
body: How about just having one constant for the authorization endpoints and another for the means?
created_at: 2025-04-24T11:27:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23,6 +23,10 @@ public interface MaatCourtDataApiClient {
     @GetExchange("/financial-assessments/{financialAssessmentId}")
     FinancialAssessmentDTO getFinancialAssessment(@PathVariable Integer financialAssessmentId);
 
+    @PatchExchange("/financial-assessments/rollback/{financialAssessmentId}")
+    void patchFinancialAssessment(@RequestBody Map<String, Object> updateFields,
+                                  @PathVariable Integer financialAssessmentId);
path: crime-means-assessment/src/main/java/uk/gov/justice/laa/crime/meansassessment/client/MaatCourtDataApiClient.java
user: login: Mhowell494
body: Looks good, but may cause some confusion given the URL. I think we need a ticket to update the URL in the Court Data API & affected services to better reflect the purpose of the endpoint - partial updates not limited to rollbacks.
created_at: 2025-04-24T14:59:51Z
author_association: CONTRIBUTOR

PR comments:
user: login: sonarqubecloud[bot]
created_at: 2025-04-24T13:43:56Z
body: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=ministryofjustice_laa-crime-means-assessment&pullRequest=482) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=ministryofjustice_laa-crime-means-assessment&pullRequest=482&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=ministryofjustice_laa-crime-means-assessment&pullRequest=482&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=ministryofjustice_laa-crime-means-assessment&pullRequest=482&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [95.3% Coverage on New Code](https://sonarcloud.io/component_measures?id=ministryofjustice_laa-crime-means-assessment&pullRequest=482&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=ministryofjustice_laa-crime-means-assessment&pullRequest=482&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=ministryofjustice_laa-crime-means-assessment&pullRequest=482)


author_association: NONE