Skip to content

Commit 2df2589

Browse files
authored
Fix #5194: allow serializing Throwables even with custom visibility settings (#5220)
1 parent ad3b119 commit 2df2589

File tree

4 files changed

+81
-4
lines changed

4 files changed

+81
-4
lines changed

pom.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,9 @@
391391
<jdk>17</jdk>
392392
</activation>
393393
<properties>
394-
<argLine>--add-opens=java.base/java.util=ALL-UNNAMED</argLine>
394+
<argLine>--add-opens=java.base/java.lang=ALL-UNNAMED
395+
--add-opens=java.base/java.util=ALL-UNNAMED
396+
</argLine>
395397
</properties>
396398
<build>
397399
<plugins>
@@ -454,7 +456,9 @@
454456
<jdk>[21,)</jdk>
455457
</activation>
456458
<properties>
457-
<argLine>--add-opens=java.base/java.util=ALL-UNNAMED</argLine>
459+
<argLine>--add-opens=java.base/java.lang=ALL-UNNAMED
460+
--add-opens=java.base/java.util=ALL-UNNAMED
461+
</argLine>
458462
</properties>
459463
<build>
460464
<plugins>

release-notes/VERSION-2.x

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ Project: jackson-databind
2222
#5192: Record types are broken on Android when using R8
2323
(reported by @HelloOO7)
2424
(fix by @pjfanning)
25+
#5194: Custom `Throwable` not serializable if using `JsonAutoDetect` settings
26+
that only detect Fields
27+
(reported by @riskop)
2528
#5197: Add more informative exception for back-references with `record` type
2629
(fix by Joo-Hyuk K)
2730
- Generate SBOMs [JSTEP-14]

src/main/java/com/fasterxml/jackson/databind/ser/BeanPropertyWriter.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -944,15 +944,27 @@ protected boolean _handleSelfReference(Object bean, JsonGenerator gen,
944944
throws IOException
945945
{
946946
if (!ser.usesObjectId()) {
947+
boolean writeAsNull = false;
948+
947949
if (prov.isEnabled(SerializationFeature.FAIL_ON_SELF_REFERENCES)) {
948950
// 05-Feb-2013, tatu: Usually a problem, but NOT if we are handling
949951
// object id; this may be the case for BeanSerializers at least.
950952
// 13-Feb-2014, tatu: another possible ok case: custom serializer
951953
// (something OTHER than {@link BeanSerializerBase}
952954
if (ser instanceof BeanSerializerBase) {
953-
prov.reportBadDefinition(getType(), "Direct self-reference leading to cycle");
955+
// 09-Jul-2025, tatu: [databind#5194] Let's suppress specific case
956+
// of "cause" for Throwables
957+
if (_isThrowableFieldCause(prov, bean)) {
958+
writeAsNull = true;
959+
} else {
960+
prov.reportBadDefinition(getType(), "Direct self-reference leading to cycle");
961+
}
954962
}
955-
} else if (prov.isEnabled(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL)) {
963+
} else {
964+
writeAsNull = prov.isEnabled(SerializationFeature.WRITE_SELF_REFERENCES_AS_NULL);
965+
}
966+
967+
if (writeAsNull) {
956968
if (_nullSerializer != null) {
957969
// 23-Oct-2019, tatu: Tricky part -- caller does not specify if it's
958970
// "as property" (in JSON Object) or "as element" (JSON array, via
@@ -970,6 +982,17 @@ protected boolean _handleSelfReference(Object bean, JsonGenerator gen,
970982
return false;
971983
}
972984

985+
// Helper method to recognize `Throwable.cause` Field, which has "this" as initialized
986+
// value to mean "not set" (`Throwable.getCause()` translates this to `null`).
987+
//
988+
// @since 2.20
989+
private boolean _isThrowableFieldCause(SerializerProvider prov, Object bean) {
990+
return bean instanceof Throwable
991+
&& _member instanceof AnnotatedField
992+
&& "cause".equals(_name.getValue())
993+
;
994+
}
995+
973996
@Override
974997
public String toString() {
975998
StringBuilder sb = new StringBuilder(40);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.fasterxml.jackson.databind.ser.jdk;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import com.fasterxml.jackson.annotation.JsonAutoDetect;
6+
import com.fasterxml.jackson.annotation.PropertyAccessor;
7+
import com.fasterxml.jackson.databind.*;
8+
import com.fasterxml.jackson.databind.json.JsonMapper;
9+
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
10+
11+
import static org.junit.jupiter.api.Assertions.assertNotNull;
12+
13+
public class CustomExceptionSer5194Test
14+
extends DatabindTestUtil
15+
{
16+
static class MyIllegalArgumentException extends RuntimeException {
17+
private static final long serialVersionUID = 1L;
18+
19+
public MyIllegalArgumentException() {
20+
super();
21+
}
22+
23+
public MyIllegalArgumentException(String s) {
24+
super(s);
25+
}
26+
27+
public MyIllegalArgumentException(String message, Throwable cause) {
28+
super(message, cause);
29+
}
30+
31+
public MyIllegalArgumentException(Throwable cause) {
32+
super(cause);
33+
}
34+
}
35+
36+
// [databind#5194]: failed to serialize custom exception
37+
@Test
38+
public void test5194() throws Exception {
39+
ObjectMapper mapper = JsonMapper.builder()
40+
.visibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
41+
.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
42+
.build();
43+
44+
String json = mapper.writeValueAsString(new MyIllegalArgumentException());
45+
assertNotNull(json);
46+
}
47+
}

0 commit comments

Comments
 (0)