0

I have one method throwing MyOwnException, but we all know that it is suggested to wrap it with RunTimeException when we are calling within streams. So here is the implementation I have done:

package com.company;

import java.util.Arrays;
import java.util.List;

public class Main {
    static class MyOwnException extends Exception {

    }
    public static void main(String[] args) throws MyOwnException {
    // write your code here
        List<String> input = Arrays.asList("bad", "ok");
        try {
            long i = input.parallelStream().map(s -> tryIfBadStringThrowExceptionIdentity(s)).count();    
        }
        catch (RuntimeException re) {
            String s = re.getMessage();
            if(s.equalsIgnoreCase("MyOwnException"))
                throw new MyOwnException();
        }
    }

    private static String ifBadStringThrowExceptionIdentity(String s) throws MyOwnException {
        if(s.equalsIgnoreCase("bad"))
            throw new MyOwnException();
        else return s;
    }
    
    private static String tryIfBadStringThrowExceptionIdentity(String s)    {
        try {
            return ifBadStringThrowExceptionIdentity(s);
        } catch (MyOwnException e) {
            throw new RuntimeException("MyOwnException");
        }
    }
}

Passing string and then again creating exception back via the string, is it best practices, or you have some elegant solution to this? if this is best way, am I the only one started disliking Java? And by the way, why only RunTimeException is valid for Streams related exception, does it impact code speed converting string back to exception?

  • 1
    Wouldn't it be simplier if MyOwnException was a subclass of RuntimeException? i.e. `static class MyOwnException extends RuntimeException` – Guillaume Sep 08 '20 at 18:54
  • What if the exception is already there and is used at multiple places, can changing that is a good decision at multiple places? – kya aap khush hain Sep 08 '20 at 21:41
  • Please take the myOwnException and ifBadStringThrowExceptionIdentity prototypes fixed, as they are used already at multiple places, and changing those doesn't seem optimised way of going about it, this code is just written for the sake of giving a full picture of the problem, main issue is passing the exception which is already there with the fixed exception, now how to go about throwing same exception when we use streams. – kya aap khush hain Sep 08 '20 at 21:43
  • This is sadly not that easily solvable, see also https://stackoverflow.com/q/27644361. There are tricks which allow you to throw checked exceptions without the need of wrapping them, however these tricks are discouraged (by some people) because they [break the type system](https://stackoverflow.com/questions/27644361#comment43718733_27644361). Also throwing plain `RuntimeException`s is [discouraged](https://rules.sonarsource.com/java/RSPEC-112). Maybe it would be better to wrap the checked exception in a custom `RuntimeException` subclass. – Marcono1234 Sep 08 '20 at 22:17
  • Just now I did research on this and came to know there is no direct way to do it, java is in suicidal mode, so the question is what I am doing, is that best way or some smart way exist? – kya aap khush hain Sep 08 '20 at 22:21

1 Answers1

1

Do not use an existing exception type like RuntimeException that can have other causes and don’t rely on something as fragile as strings for identifying your issue. Just use a dedicated exception type for carrying an underlying checked exception to the initiator:

static class UncheckedMyOwnException extends RuntimeException {
    UncheckedMyOwnException(MyOwnException original) {
        super(original);
    }
    @Override
    public MyOwnException getCause() {
        return (MyOwnException)super.getCause();
    }
}
public static void main(String[] args) throws MyOwnException {
    List<String> input = Arrays.asList("bad", "ok");
    try {
        // do not rely on count() for processing code; starting with JDK 9 it will skip
        // map operations not needed to determine the count
        input.parallelStream().map(s -> tryIfBadStringThrowExceptionIdentity(s))
            .forEach(System.out::println);
    }
    catch(UncheckedMyOwnException re) {
        throw re.getCause();
    }
}
private static String ifBadStringThrowExceptionIdentity(String s) throws MyOwnException {
    if(s.equalsIgnoreCase("bad"))
        throw new MyOwnException();
    else return s;
}
private static String tryIfBadStringThrowExceptionIdentity(String s)    {
    try {
        return ifBadStringThrowExceptionIdentity(s);
    } catch(MyOwnException e) {
        throw new UncheckedMyOwnException(e);
    }
}

Note that this will throw the original exception, so it includes the actual origin in its stack trace. But when the exception happened in a worker thread, it will not include the initiator, i.e. the call chain that started the stream operation. To ensure that both parts are always included, you would have to use something like

catch(UncheckedMyOwnException re) {
    MyOwnException withInitiator = new MyOwnException();
    withInitiator.initCause(re.getCause());
    throw withInitiator;
}

Using a dedicated unchecked exception type to carry a checked exception, is an established pattern, see for example UncheckedIOException.

Holger
  • 285,553
  • 42
  • 434
  • 765