1
  • Spring boot starter version: 2.3.4.RELEASE
  • SPRINGWEB version 5.2.8.RELEASE

My get requests started to respond with extraneous "OK" ErrorAttributes appended to the response body, which breaks all json deserialization on the upstream clients

HTTP/1.1 200 
Date: Tue, 26 Jan 2021 07:44:00 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
X-Trace-Id: 55418518fcaa412b
X-Span-Id: 55418518fcaa412b
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: HEAD, GET, OPTIONS, POST, PUT, PATCH, DELETE
Access-Control-Max-Age: 3600
Access-Control-Allow-Headers: *
Strict-Transport-Security: max-age=63072000; includeSubdomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff

{
    "id": "ab160a6c-fe00-4eff-a63d-7c10e1c0607c",
    "nickName": "Test User",
    "emailAddress": "test.user@test-domain.com",
    "phoneNumber": null,
    "firstName": "Test",
    "lastName": "User'",
    "location": {
        "id": "a77381f2-5c89-4134-bdad-229ad5e7c9e2",
        "latitude": null,
        "longitude": null,
        "country": "DE",
        "stateOrRegion": null,
        "city": null
    },,
    "avatarUrl": null,
    "role": "DEFAULT",
    "invitationLink": "https://www.test-domain.com/app/app-invite/b666ee33-2046-4ea3-af80-f310756a2eac",
    "invitee": null
}{
    "timestamp": "2021-01-26T07:44:00.826+00:00",
    "status": 200,
    "error": "OK",
    "message": "",
    "path": "/api/users/ab160a6c-fe00-4eff-a63d-7c10e1c0607c"
}

I am stuck on this for 2 days, and would like to know if anyone knows about this problem and how to solve it.

Update i have exception handlers, that return a different json model:


  @ResponseBody
  @ResponseStatus(HttpStatus.CONFLICT)
  @ExceptionHandler(MethodArgumentNotValidException.class)
  public ErrorModel handleValidationExceptions(@Nonnull final MethodArgumentNotValidException ex) {
    log.error("Error executing request:", ex);

    final List<PropertyErrorModel> propertyErrors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(this::buildProperty)
            .collect(toList());
    final List<String> globalMessages = ex.getBindingResult().getGlobalErrors()
            .stream()
            .map(ObjectError::getDefaultMessage)
            .filter(Objects::nonNull)
            .collect(toList());
    return getErrorModelBuilder()
            .globalErrors(globalMessages)
            .propertyErrors(propertyErrors)
            .build();
  }

  @ResponseBody
  @ResponseStatus(HttpStatus.CONFLICT)
  @ExceptionHandler(ConstraintViolationException.class)
  public ErrorModel handleConstraintViolation(@Nonnull final ConstraintViolationException ex) {
    log.error("Error executing request:", ex);

    final List<PropertyErrorModel> propertyErrors = ex.getConstraintViolations()
            .stream()
            .map((viol) -> {
              return PropertyErrorModel.builder()
                      .property(viol.getPropertyPath().toString())
                      .rejectedValue(viol.getInvalidValue())
                      .message(viol.getMessage())
                      .build();
            })
            .collect(toList());
    final List<String> globalMessages = List.of(ex.getMessage());
    return getErrorModelBuilder()
            .code("validation.invalid.property")
            .globalErrors(globalMessages)
            .propertyErrors(propertyErrors)
            .build();
  }

  @ResponseBody
  @ResponseStatus(HttpStatus.CONFLICT)
  @ExceptionHandler(ValidationException.class)
  public ErrorModel handleValidationException(@Nonnull final ValidationException ex) {
    log.error("Error executing request:", ex);

    final ErrorModel error = ex.getError();
    if (error != null) {
      return getErrorModelBuilder()
              .code(firstNonNull(error.getCode(), "validation.failed"))
              .globalErrors(error.getGlobalErrors())
              .propertyErrors(error.getPropertyErrors())
              .build();
    }

    final List<PropertyErrorModel> propertyErrors = emptyIfNull(ex.getPropertyErrors());
    final List<String> globalMessages = List.of(ex.getMessage());
    return getErrorModelBuilder()
            .code("validation.failed")
            .globalErrors(globalMessages)
            .propertyErrors(propertyErrors)
            .build();
  }

  @SneakyThrows
  @ResponseBody
  @ResponseStatus(HttpStatus.CONFLICT)
  @ExceptionHandler(AlreadyExistsException.class)
  public ErrorModel handleAlreadyExistsException(@Nonnull final AlreadyExistsException ex) {
    log.error("Error executing request:", ex);

    final Object domain = ex.getDomain();
    if (domain == null) {
      final List<String> globalMessages = List.of(ex.getMessage());
      return getErrorModelBuilder()
              .code("validation.failed")
              .globalErrors(globalMessages)
              .build();
    }

    final String domainJson = objectMapper.writeValueAsString(domain);
    final List<String> globalMessages = List.of(ex.getMessage(), domainJson);
    return getErrorModelBuilder()
            .code("validation.failed")
            .globalErrors(globalMessages)
            .build();
  }

  @ResponseBody
  @ResponseStatus(HttpStatus.CONFLICT)
  @ExceptionHandler(UnsupportedFilterPropertyException.class)
  public ErrorModel handleUnsupportedFilterPropertyException(@Nonnull final UnsupportedFilterPropertyException ex) {
    log.error("Error executing request:", ex);

    if (ex.getPropertyError() != null) {
      return getErrorModelBuilder()
              .code("validation.failed")
              .propertyErrors(ex.getPropertyError())
              .build();
    }

    final List<String> globalMessages = List.of(ex.getMessage());
    return getErrorModelBuilder()
            .code("validation.failed")
            .globalErrors(globalMessages)
            .build();
  }

  @ResponseBody
  @ResponseStatus(HttpStatus.NOT_FOUND)
  @ExceptionHandler(NotFoundException.class)
  public ErrorModel handleNotFoundException(@Nonnull final NotFoundException ex) {
    log.warn("Error executing request:", ex);

    final List<String> globalMessages = List.of(ex.getMessage());
    return getErrorModelBuilder()
            .code("resource.notfound")
            .globalErrors(globalMessages)
            .build();
  }

  @ResponseBody
  @ExceptionHandler(Throwable.class)
  public ResponseEntity<ErrorModel> fallbackErrorHandler(@Nonnull final Throwable ex) {
    log.error("Error executing request:", ex);

    final Class<?> errorClass = ex.getClass();
    return Optional.ofNullable(errorCodedExceptions.get(errorClass))
            .map((errorCode) -> getCodedError(ex, errorCode))
            .orElseGet(() -> getDefaultInternalServerError(ex));
  }
maress
  • 3,533
  • 1
  • 19
  • 37
  • Have you defined any exception handlers? – tzortzik Jan 26 '21 at 07:56
  • I do have exception handlers, and they are exception specific handlers – maress Jan 26 '21 at 07:57
  • 2
    I have not seen this ever, the code behind it could help. – tzortzik Jan 26 '21 at 07:59
  • 1
    Put a **breakpoint** on the return statement in your `@Controller` method, then step out of the method and step through the Spring code, to see how the actual response body is generated. [What is a debugger and how can it help me diagnose problems?](https://stackoverflow.com/q/25385173/5221149) – Andreas Jan 26 '21 at 08:21
  • or try to put an breakpoint on any caught and uncaught exception after leaving the get-method in your controller – Datz Jan 26 '21 at 08:38
  • is there an exception in the log in the first place? – Turo Jan 26 '21 at 08:59
  • It is `200` OK, and there are no error logs, unfortunately. – maress Jan 26 '21 at 10:00
  • @Andreas I have integration tests, and unfortunately, i cannot replicate it in integration tests, using both MockMvc, and rest-assured. It only happens when i deploy to my dev environment. – maress Jan 26 '21 at 10:02

1 Answers1

1

Issue caused by good old dirty context in filter. A filter execution failed to clean up data context when an exception was thrown, but unfortunately, this failed too early before the tracing kicked in. The error log never appeared in our kibana logs with appropriate trace.id. (our trace filter was not of high priority enough)

What needs more investigation, however, is how the context and the failure in the filter, which is totally unrelated to spring framework, still allowed the filter chain execution to proceed and call the controller and return 200 and then append a seemingly ErrorAttribute response with a 200 "OK" message.

maress
  • 3,533
  • 1
  • 19
  • 37