spring-projects/spring-security · error · HttpMessageNotReadableException

An error occurred reading the Token Introspection Response…

Error message

An error occurred reading the Token Introspection Response: + ex.getMessage()

What it means

OAuth2TokenIntrospectionHttpMessageConverter.readInternal wraps any exception raised while reading a token introspection response body as JSON and converting it to an OAuth2TokenIntrospection. The body is parsed into a Map<String,Object> by the JSON converter and then mapped by tokenIntrospectionConverter.convert(); any parse or mapping failure is rethrown as org.springframework.http.converter.HttpMessageNotReadableException with the cause attached. This means the introspection response could not be read into the typed model.

Solutions

  1. Check the exception cause to distinguish JSON syntax failure from field-mapping failure; log the raw response body.
  2. Check the HTTP status before parsing — only feed 2xx application/json responses to the converter.
  3. Ensure the introspection endpoint returns RFC 7662-compliant JSON (boolean active, correct claim types).
  4. Verify a JSON-capable converter is set via setJsonMessageConverter and Content-Type is application/json.
  5. Correct or relax a custom tokenIntrospectionConverter if it rejects otherwise-valid claims.

Example fix

// before
ClientHttpResponse resp = execute(introspectionRequest);
OAuth2TokenIntrospection i = converter.read(OAuth2TokenIntrospection.class, resp); // throws on 401 HTML body
// after
ClientHttpResponse resp = execute(introspectionRequest);
if (resp.getStatusCode() != HttpStatus.OK) {
  throw new OAuth2IntrospectionException("Introspection failed: " + resp.getStatusCode());
}
OAuth2TokenIntrospection i = converter.read(OAuth2TokenIntrospection.class, resp);
Defensive patterns

Strategy: validation

Validate before calling

// before reading
if (response.getStatusCode() != HttpStatus.OK) throw new OAuth2IntrospectionException("bad status " + response.getStatusCode());
if (!response.getHeaders().getContentType().isCompatibleWith(MediaType.APPLICATION_JSON)) throw new OAuth2IntrospectionException("non-JSON body");

Try / catch

try {
  return converter.read(OAuth2TokenIntrospection.class, response);
} catch (HttpMessageNotReadableException e) {
  logger.warn("Unreadable introspection response: {}", e.getCause().toString());
  return OAuth2TokenIntrospection.builder().active(false).build(); // fail closed
}

Prevention

When it happens

Trigger: Calling OAuth2TokenIntrospectionHttpMessageConverter.read() (via readInternal) when the response body is not valid JSON (HTML error page, empty body, wrong Content-Type), the JSON converter throws, or tokenIntrospectionConverter.convert() rejects the parameters (e.g. active flag not boolean, invalid claim types).

Common situations: The introspection endpoint returns 401/404 with an HTML or empty body that still gets passed to the converter; a proxy/gateway rewrites Content-Type so the Jackson converter refuses the read; an older or non-conformant authorization server omits required claims or sends them with unexpected types; custom converters configured via setTokenIntrospectionConverter are too strict.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/c687c4d220ae9e75. Report an issue: GitHub.

Appendix: source

Thrown at oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/http/converter/OAuth2TokenIntrospectionHttpMessageConverter.java:92

		this.jsonMessageConverter = converter;
	}

	@Override
	protected boolean supports(Class<?> clazz) {
		return OAuth2TokenIntrospection.class.isAssignableFrom(clazz);
	}

	@Override
	@SuppressWarnings("unchecked")
	protected OAuth2TokenIntrospection readInternal(Class<? extends OAuth2TokenIntrospection> clazz,
			HttpInputMessage inputMessage) throws HttpMessageNotReadableException {
		try {
			Map<String, Object> tokenIntrospectionParameters = (Map<String, Object>) this.jsonMessageConverter
				.read(STRING_OBJECT_MAP.getType(), null, inputMessage);
			return this.tokenIntrospectionConverter.convert(tokenIntrospectionParameters);
		}
		catch (Exception ex) {
			throw new HttpMessageNotReadableException(
					"An error occurred reading the Token Introspection Response: " + ex.getMessage(), ex, inputMessage);
		}
	}

	@Override
	protected void writeInternal(OAuth2TokenIntrospection tokenIntrospection, HttpOutputMessage outputMessage)
			throws HttpMessageNotWritableException {
		try {
			Map<String, Object> tokenIntrospectionResponseParameters = this.tokenIntrospectionParametersConverter
				.convert(tokenIntrospection);
			this.jsonMessageConverter.write(tokenIntrospectionResponseParameters, STRING_OBJECT_MAP.getType(),
					MediaType.APPLICATION_JSON, outputMessage);
		}
		catch (Exception ex) {
			throw new HttpMessageNotWritableException(
					"An error occurred writing the Token Introspection Response: " + ex.getMessage(), ex);
		}
	}

View on GitHub (pinned to 96852e8860)