1

The problem is as follows:

There is an ErrorMessageBuilder, instances of the class that implements this interface can generate messages based on exceptions that are passed to them, in addition, there is an option to check if the class can handle the passed exception or not.

interface ErrorMessageBuilder<T extends Exception> {
    String buildMessage(T exception);
    boolean canHandle(Class<? extends Exception> clazz);
}

Next, there is the class ExceptionHandlerPair which is used to create pairs (Exception+Handler)

class ExceptionHandlerPair<T extends Exception>{
    private final Class<T> clazz;
    private final ErrorMessageBuilder<? extends Exception> builder;

    public ExceptionHandlerPair(Class<T> clazz, ErrorMessageBuilder<? extends Exception> builder) {
        if(!builder.canHandle(clazz)){
            throw new IllegalArgumentException("Handler can't handle this exception");
        }

        this.clazz = clazz;
        this.builder = builder;
    }

    public boolean canHandle(Class<? extends Exception> clazz) {
        return this.clazz.isAssignableFrom(clazz);
    }

    public ErrorMessageBuilder<? extends Exception> getBuilder() {
        return builder;
    }
}

After that I try to create a Bilder and create pairs. For example I create a RuntimeExceptionErrorMessageBuilder which can handle any RuntimeException:

class RuntimeExceptionErrorMessageBuilder implements ErrorMessageBuilder<RuntimeException> {
    public String buildMessage(RuntimeException exception) {
        return "Some runtime exception";
    }

    @Override
    public boolean canHandle(Class<? extends Exception> clazz) {
        return RuntimeException.class.isAssignableFrom(clazz);
    }
}
public class SomeClass {
    public static void main(String[] args) {
        var builder = new RuntimeExceptionErrorMessageBuilder();

//        builder.canHandle(NullPointerException.class); // ? -> true
//        builder.canHandle(IllegalArgumentException.class); // ? -> true
//        builder.canHandle(Exception.class); // ? -> false

        var pair = new ExceptionHandlerPair<>(NullPointerException.class, builder);

        if(pair.canHandle(NullPointerException.class)){ // ? -> true
            var message = pair.getBuilder().buildMessage(new NullPointerException()); // <- error Required type: capture of ? extends Exception,  Provided: IllegalArgumentException
            System.out.println(message);
        }

        if(pair.canHandle(IllegalArgumentException.class)){ // ? -> true
            var message = pair.getBuilder().buildMessage(new IllegalArgumentException()); // <- error Required type: capture of ? extends Exception,  Provided: IllegalArgumentException
            System.out.println(message);
        }
    }
}

We get an error that the buildMessage(x) parameter gives an error that the x parameter should be a descendant of Exception, but IllegalArgumentException is a descendant, what is the problem?

Okay, I thought that if this restriction applies to parameters, then let's add an ExceptionWrapper like this:

class ErrorWrapper<T extends Exception>{
    private final T exception;

    public ErrorWrapper(T exception) {
        this.exception = exception;
    }

    public T getException() {
        return exception;
    }
}

After that modify ErrorMessageBuilder and RuntimeExceptionErrorMessageBuilder with ErrorWrapper in mind:

interface ErrorMessageBuilder<T extends Exception> {
    String buildMessage(ErrorWrapper<T> exception);
    boolean canHandle(Class<? extends Exception> clazz);
}

class RuntimeExceptionErrorMessageBuilder implements ErrorMessageBuilder<RuntimeException> {
    public String buildMessage(ErrorWrapper<RuntimeException> exception) {
        return "Some runtime exception";
    }

    @Override
    public boolean canHandle(Class<? extends Exception> clazz) {
        return RuntimeException.class.isAssignableFrom(clazz);
    }
}

Then change the main and try to create messages with the changes, but it does not work even if I try to fool java I get: error Required type: ErrorWrapper <capture of ? extends Exception>, Provided: ErrorWrapper <capture of ? extends Exception>

public class SomeClassWrapper {
    public static void main(String[] args) {
        var builder = new RuntimeExceptionErrorMessageBuilder();

        ExceptionHandlerPair<NullPointerException> pair = new ExceptionHandlerPair<>(NullPointerException.class, builder);

        if(pair.canHandle(NullPointerException.class)){ // ? -> true
            ErrorWrapper<NullPointerException> npeWrapper = new ErrorWrapper<>(new NullPointerException());

            var message = pair.getBuilder().buildMessage(npeWrapper); // <- error Required type: ErrorWrapper <capture of ? extends Exception>,  Provided: ErrorWrapper<NullPointerException
            System.out.println(message);
        }

        // 
        if(pair.canHandle(NullPointerException.class)){ // ? -> true
            // and here we'll try to swoon over Java!!! :)
            ErrorWrapper<? extends Exception> npeWrapper = new ErrorWrapper<>(new NullPointerException());

            var message = pair.getBuilder().buildMessage(npeWrapper); // <- error Required type: ErrorWrapper <capture of ? extends Exception>,  Provided: ErrorWrapper <capture of ? extends Exception>
            System.out.println(message);
        }
    }
}

I would be grateful if someone could explain this behavior!!! Thanks in advance!

Mher Arsh
  • 588
  • 4
  • 21

2 Answers2

2

As I see it, the fix is to introduce another generic type variable into your ExceptionHandlerPair class. In the code below, the type variable T is the type of exception that the ErrorMessageBuilder handles, and U is the type of exception that the canHandle method will return true for:

class ExceptionHandlerPair<T extends Exception, U extends T>{
    private final Class<U> clazz;
    private final ErrorMessageBuilder<T> builder;

    public ExceptionHandlerPair(Class<U> clazz, ErrorMessageBuilder<T> builder) {
        if(!builder.canHandle(clazz)){
            throw new IllegalArgumentException("Handler can't handle this exception");
        }

        this.clazz = clazz;
        this.builder = builder;
    }

    public boolean canHandle(Class<? extends Exception> clazz) {
        return this.clazz.isAssignableFrom(clazz);
    }

    public ErrorMessageBuilder<T> getBuilder() {
        return builder;
    }
}

I made this change to your code and your SomeClass class compiled.

The problem with using a wildcard (the ?) is that you are throwing away the information on what type of exception the builder handles. In your code, pair.getBuilder() will always return ErrorMessageBuilder<? extends Exception>, no matter how your pair was constructed. The type of exception handled by this builder will be some arbitrary subclass of Exception, but you don't know what. It could be an ErrorMessageBuilder<Exception>, or a ErrorMessageBuilder<RuntimeException>, and if this was the case, you could pass a NullPointerException to the .getBuilder() method. However, the unbounded wildcard also allows the getBuilder() method to return an ErrorMessageBuilder<NegativeArraySizeException>, for example, and you can't pass a NullPointerException to the buildMessage() method of that.

Luke Woodward
  • 63,336
  • 16
  • 89
  • 104
1

What is causing this error is that you are using wild cards as return types in the following method ExceptionHandlerPair.getBuilder() and this is not recommended as it causes ambiguity for the compilier you can check the below discussions for more details on why this is not recommended Generic wildcard types should not be used in return parameters

https://rules.sonarsource.com/java/RSPEC-1452

It is highly recommended not to use wildcard types as return types. Because the type inference rules are fairly complex it is unlikely the user of that API will know how to use it correctly. Let’s take the example of method returning a "List<? extends Animal>". Is it possible on this list to add a Dog, a Cat, …​ we simply don’t know. And neither does the compiler, which is why it will not allow such a direct use. The use of wildcard types should be limited to method parameters. This rule raises an issue when a method returns a wildcard type.

Instead you can simply change the return type of getBuilder() to ErrorMessageBuilder<T>

public class ExceptionHandlerPair<T extends Exception> {

//~ ----------------------------------------------------------------------------------------------------------------
//~ Instance fields 
//~ ----------------------------------------------------------------------------------------------------------------

public final Class<T> clazz;
private final ErrorMessageBuilder<T> builder;

//~ ----------------------------------------------------------------------------------------------------------------
//~ Constructors 
//~ ----------------------------------------------------------------------------------------------------------------

public ExceptionHandlerPair(Class<T> clazz, ErrorMessageBuilder<T> builder) {
    if (!builder.canHandle(clazz)) {
        throw new IllegalArgumentException("Handler can't handle this exception");
    }

    this.clazz = clazz;
    this.builder = builder;
}

//~ ----------------------------------------------------------------------------------------------------------------
//~ Methods 
//~ ----------------------------------------------------------------------------------------------------------------

public boolean canHandle(Class<? extends Exception> clazz) {
    return this.clazz.isAssignableFrom(clazz);
}

public ErrorMessageBuilder<T> getBuilder() {
    return builder;
}

}

  • 1
    you are absolutely right, I know that you can not return undefined types, I figured out what the problem is, unfortunately the option you proposed does not work because I have a different type `ExceptionHandlerPair` from the type `ErrorMessageBuilder`, for example `ExceptionHandlerPair ` and `ErrorMessageBuilder` which you can see in my example, the point is that I want to have one Builder for different types of exceptions – Mher Arsh Apr 09 '23 at 17:29