Affects: 3.1.5
Hello.
I am using id 'org.springframework.boot' version '3.1.5' and id 'org.graalvm.buildtools.native' version '0.9.27'
When making a rest response with ProblemDetail that contain some property it is expected that this property will be rendered as top level key-value pair in the output JSON, and it does work so in JVM mode. While in native image it will always return properties like a "properties" sub map.
Please see example below: 1. In JVM - displayed as expected:
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "User not found",
"instance": "/oauth2/management/users/d44cdf72-8417-5963-85ae-f1936288ae82/identities",
"properties": {
"userId": "d44cdf72-8417-5963-85ae-f1936288ae82"
},
"traceId": "8e18eedd0d9092798fbf8d4cb4c9ca00"
}
- In Native image the same entity - displayed as sub-map:
{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "User not found",
"instance": "/oauth2/management/users/d44cdf72-8417-5963-85ae-f1936288ae82/identities",
"properties": {
"properties": {
"userId": "d44cdf72-8417-5963-85ae-f1936288ae82"
},
"traceId": "fc9ced45f90fa96fa6a81148bf06e5cc"
}
}
Comment From: snicoll
@denysandriyanov thanks for the report, but unfortunately I can't reproduce this with:
problemDetail.setProperty("userId", UUID.randomUUID().toString());
problemDetail.setProperty("traceId", UUID.randomUUID().toString());
http :8080/spring-projects/spring-nope
HTTP/1.1 404
Connection: keep-alive
Content-Type: application/problem+json
Date: Tue, 14 Nov 2023 19:03:57 GMT
Keep-Alive: timeout=60
Transfer-Encoding: chunked
{
"detail": "No project found with slug 'spring-nope'",
"instance": "/spring-projects/spring-nope",
"properties": {
"traceId": "f881377f-1df8-49e7-aca9-e7be6006f59f",
"userId": "d0d71e6a-fb4b-4a66-a04f-46182dff5666"
},
"status": 404,
"title": "Unknown Project",
"type": "https://example.org/problems/unknown-project"
}
Rather than the output, we'd need a small sample that we can run ourselves to understand how your use case differs. You can attach a zip to this issue or push the code to a separate GitHub repository.
Comment From: denysandriyanov
to give a bit more context.
I am building ProblemDetail like this
public ProblemDetail build(int statusCode, String title, String message, Object properties) {
ProblemDetail problemDetail = ProblemDetail.forStatus(statusCode);
problemDetail.setTitle(title);
problemDetail.setDetail(message);
if (properties != null) {
problemDetail.setProperty(PROPERTIES, properties);
}
Optional<String> currentTraceId = ofNullable(tracer.spanBuilder(AUTH_CONSENT_SPAN).startSpan())
.map(Span::getSpanContext)
.map(SpanContext::getTraceId);
currentTraceId.ifPresent(traceId -> problemDetail.setProperty(TRACE_ID, traceId));
return problemDetail;
}
and then i return it in ExceptionHandler
@ExceptionHandler(AuthIdentityException.class)
ProblemDetail authIdentityExceptionHandler(AuthIdentityException e) {
log.error(e.getMessage(), e);
return problemDetailBuilder.build(e.getStatusCode(), e.getTitle(), e.getMessage(), e.getProperties());
}
The Exception is
public class AuthIdentityException extends RuntimeException {
private final String title;
private final int statusCode;
private final transient Object properties;
public AuthIdentityException(String title, int statusCode, String message, Object properties, Throwable cause) {
super(message, cause);
this.statusCode = statusCode;
this.title = title;
this.properties = properties;
}
}
Where Object properties in my example is Map
Hope that helps. Also, just make sure you use Native Image build
Comment From: snicoll
Hope that helps.
Not really. We'd need to move all that code in text into something that we can run and, as we do, we may miss a step that you're not showing. As I've asked before, can you please share a sample project we can run ourselves, not code snippet in text.
Comment From: denysandriyanov
let me make a project
Comment From: denysandriyanov
Please see attached reproducer.zip
Comment From: denysandriyanov
Please see attached reproducer.zip
As a side note, as a description for setProperty method in ProblemDetail there is
When Jackson JSON is present on the classpath, any properties set here are rendered as top level key-value pairs in the output JSON. Otherwise, they are rendered as a "properties" sub-map.
May be in native image it can not detect it?
Comment From: snicoll
@denysandriyanov unfortunately, the entire application is not what I have in mind. It doesn't even start for me:
FAILURE: Build failed with an exception.
* Where:
Build file '/Users/snicoll/Downloads/reproducer/build.gradle' line: 31
* What went wrong:
A problem occurred evaluating root project 'auth-identity-service'.
> /Users/snicoll/Downloads/reproducer/VERSION (No such file or directory)
I am looking at the code and I don't even see what creates AuthIdentityException
. Can you please simplify?
Comment From: denysandriyanov
Please see attached.
There is a DummyController.java class, when you call it it will throw
Comment From: snicoll
@denysandriyanov I am happy to try to help but please make sure this can be started? The application does not start (and doesn't require Java 20 either):
{"@timestamp":"2023-11-15T11:05:26.675Z","log.level":"ERROR","message":"\n\n***************************\nAPPLICATION FAILED TO START\n***************************\n\nDescription:\n\nFailed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.\n\nReason: Failed to determine a suitable driver class\n\n\nAction:\n\nConsider the following:\n\tIf you want an embedded database (H2, HSQL or Derby), please put it on the classpath.\n\tIf you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).\n","ecs.version": "1.2.0","service.name":"auth-identity-service","event.dataset":"auth-identity-service","process.thread.name":"main","log.logger":"org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter"}
also make sure ./gradlew nativeCompile
actually works.
Comment From: snicoll
Nevermind. I ripped out 90% of the config and the reproducer didn't even enabled problem details. I've managed to reproduce it in demo project by adding a nested map that I overlooked.
Courtesy of @rstoyanchev, it looks like ProblemDetailJacksonMixin
does not work in Native.
Comment From: snicoll
The framework registers custom Mixin but does not provide the necessary hints as Spring Boot does for scanned mixins. In the meantime, you can workaround the problem by adding the following your your custom hints:
new BindingReflectionHintsRegistrar().registerReflectionHints(hints.reflection(),
ProblemDetailJacksonMixin.class);