I am writing a program that consumes some data from a RabbitMQ API, via the java.function.Consumer
functional interface. In my subclass that implements this interface, I have 3 potential exceptions:
public interface MyMessageHandler extends Consumer<MyMessage> {}
public class SpecificMessageHandler implements MyMessageHandler {
@Override
public void accept(IncomingMessage incomingMessage) {
if(incomingMessage.getTime() < 1000) {
throw new InvalidTimeException("message"); //Custom exception extends RuntimeException
}
if(incomingMessageAlreadyExists) {
throw new DuplicateMessageException("message"); //Custom exception extends RuntimeException
}
try {
ObjectMapper.reader.readValue(incomingMessage.getJson()) // Throws IOEXception
} catch(IOException e) {
throw new CustomIOException("message"); //Custom exceptin extends RuntimeException
}
// If all is well, carry on with rest of function
}
}
I am having to take this route because you can't seem to throw regular exceptions in a functional interface, it has to be a runtime exception.
I am throwing the exceptions at this level of the stack, as I want to implement the actual handling behaviour higher up the stack, due to the fact I will have many message handlers that will be handled in the same way, therefore it's easier to implement that handling behaviour once higher up, rather than in every single handler class.
This functionally works, however feels like bad design. Is there a more elegant way to implement this? Note, I can't switch from a functional interface to something else, as i'm working with legacy code (not ideal, but that's how the world is some times!)
I am having to take this route because you can't seem to throw regular exceptions in a functional interface, it has to be a runtime exception.
This is incorrect. You can't throw checked exceptions in a java.function.Consumer
specifically, because its signature
void accept(T t);
doesn't declare any.
The following functional interface is perfectly acceptable, and can throw certain checked exceptions.
public interface MyMessageHandler {
void accept(IncomingMessage incomingMessage) throws IOException;
}
MyMessageHandler handler = (msg) -> {
throw new IOException(); // Valid
};
If you wanted something more generic, you could declare something like
public interface ThrowableConsumer<T> {
void accept(T t) throws Exception;
}
interface MyMessageHandler extends ThrowableConsumer<MyMessage> {}
I am throwing the exceptions at this level of the stack, as I want to implement the actual handling behaviour higher up the stack
You could use Lombok's @SneakyThrows
to effectively convert a checked exception into an unchecked one. A hack, but it works.
class SpecificMessageHandler implements MyMessageHandler {
@Override
@SneakyThrows
public void accept(IncomingMessage incomingMessage) {
// Doesn't matter that it's checked and j.f.Consumer doesn't declare it
throw new IOException();
}
}
However I highly doubt that your employer will allow you to do hacks like this if you're not permitted to change an interface.
This functionally works, however feels like bad design
Why? There's a huge class of people who believe that checked exceptions were a mistake to begin with. There's nothing wrong with runtime exceptions.
Note, I can't switch from a functional interface to something else, as i'm working with legacy code
People throw this word around a lot. Java 8 came out 8 years ago. Your code can't be that old. Usually when people say "I can't change X", what they mean is that they don't feel comfortable changing it, for whatever reason. If you're living in fear of your software, find a way to change that.