Skip to content

ThrowableAdapterFactory is needed for Java module system #18

@gordontytler

Description

@gordontytler

The problem occurs deserializing an error response with java versions after 1.8.

To reproduce, run the Summary.java described in the README.md but deliberately use an invalid accountId or token.

Expected output

HTTP 401 : Insufficient authorization to perform request. at com.oanda.v20.ThrowableAdapterFactory$1.read(ThrowableAdapterFactory.java:53) at com.oanda.v20.ThrowableAdapterFactory$1.read(ThrowableAdapterFactory.java:35) at com.google.gson.Gson.fromJson(Gson.java:888) at com.google.gson.Gson.fromJson(Gson.java:853) at com.google.gson.Gson.fromJson(Gson.java:802) at com.google.gson.Gson.fromJson(Gson.java:774) at com.oanda.v20.Context.execute(Context.java:302) at com.oanda.v20.account.AccountContext.summary(AccountContext.java:117) at com.oanda.v20.TestSummary.main(TestSummary.java:14)

Actual output when compiled with Java 21

java.lang.reflect.InaccessibleObjectException: Unable to make field private java.lang.String java.lang.Throwable.detailMessage accessible: module java.base does not "opens java.lang" to unnamed module @7b7fdc8 at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178) at java.base/java.lang.reflect.Field.setAccessible(Field.java:172) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:157) at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:100) at com.google.gson.Gson.getAdapter(Gson.java:423) at com.google.gson.Gson.fromJson(Gson.java:887) at com.google.gson.Gson.fromJson(Gson.java:853) at com.google.gson.Gson.fromJson(Gson.java:802) at com.google.gson.Gson.fromJson(Gson.java:774) at com.oanda.v20.Context.execute(Context.java:303) at com.oanda.v20.account.AccountContext.summary(AccountContext.java:117) at Summary.main(Summary.java:13)

This can be fixed with by adding this to the GsonBuilder in Context.java

.registerTypeAdapterFactory(ThrowableAdapterFactory.INSTANCE)

Here is the ThrowableAdapterFactory

package com.oanda.v20;

import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;

import java.io.IOException;

/**
 * This is the fix for gson 2.10.1:
 * <p>
 * com.google.gson.JsonIOException:
 * Failed making field 'java.lang.Throwable#detailMessage' accessible; either increase
 * its visibility or write a custom TypeAdapter for its declaring type.
 * <p>
 * with gson 2.8.2 the error is
 * <p>java.lang.reflect.InaccessibleObjectException: Unable to make field private
 * java.lang.String java.lang.Throwable.detailMessage accessible: module java.base does not
 * "opens java.lang" to unnamed module @7b7fdc8</p>
 * see <a href="https://github.com/google/gson/issues/2352">gson/issues/2352</a>
 */
class ThrowableAdapterFactory implements TypeAdapterFactory {
    private ThrowableAdapterFactory() {}

    public static final ThrowableAdapterFactory INSTANCE = new ThrowableAdapterFactory();

    @Override
    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
        // Only handles Throwable and subclasses; let other factories handle any other type
        if (!Throwable.class.isAssignableFrom(type.getRawType())) {
            return null;
        }

        @SuppressWarnings("unchecked")
        TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<Throwable>() {
            @Override
            public Throwable read(JsonReader in) throws IOException {
                if (in.peek() == JsonToken.NULL) {
                    in.nextNull();
                    return null;
                }
                String errorMessage = "There was an error message in the response.";
                in.beginObject();
                while (in.hasNext()) {
                    String name = in.nextName();
                    if (name.equals("errorMessage")) {
                        errorMessage = in.nextString();
                    } else {
                        in.skipValue();
                    }
                }
                in.endObject();
                RequestException requestException = new RequestException();
                requestException.errorMessage = errorMessage;
                return requestException;
            }

            @Override
            public void write(JsonWriter out, Throwable value) throws IOException {
                if (value == null) {
                    out.nullValue();
                    return;
                }

                out.beginObject();
                // Include exception type name to give more context; for example NullPointerException might
                // not have a message
                out.name("type");
                out.value(value.getClass().getSimpleName());

                out.name("message");
                out.value(value.getMessage());

                Throwable cause = value.getCause();
                if (cause != null) {
                    out.name("cause");
                    write(out, cause);
                }

                Throwable[] suppressedArray = value.getSuppressed();
                if (suppressedArray.length > 0) {
                    out.name("suppressed");
                    out.beginArray();

                    for (Throwable suppressed : suppressedArray) {
                        write(out, suppressed);
                    }

                    out.endArray();
                }
                out.endObject();
            }
        };
        return adapter;
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions