Letting the presentation layer (JSF) handle business exceptions from service layer (EJB)

Solution 1:

Create a custom service layer specific runtime exception which is annotated with @ApplicationException with rollback=true.

@ApplicationException(rollback=true)
public abstract class ServiceException extends RuntimeException {}

Create some concrete subclasses for general business exceptions, such as constraint violation, required entity, and of course optimistic lock.

public class DuplicateEntityException extends ServiceException {}
public class EntityNotFoundException extends ServiceException {}
public class EntityAlreadyModifiedException extends ServiceException {}

Some of them can be thrown directly.

public void register(User user) {
    if (findByEmail(user.getEmail()) != null) {
        throw new DuplicateEntityException();
    }

    // ...
}
public void addToOrder(OrderItem item, Long orderId) {
    Order order = orderService.getById(orderId);

    if (order == null) {
        throw new EntityNotFoundException();
    }

    // ...
}

Some of them need a global interceptor.

@Interceptor
public class ExceptionInterceptor implements Serializable {

    @AroundInvoke
    public Object handle(InvocationContext context) throws Exception {
        try {
            return context.proceed();
        }
        catch (javax.persistence.EntityNotFoundException e) { // Can be thrown by Query#getSingleResult().
            throw new EntityNotFoundException(e);
        }
        catch (OptimisticLockException e) {
            throw new EntityAlreadyModifiedException(e);
        }
    }

}

Which is registered as default interceptor (on all EJBs) as below in ejb-jar.xml.

<interceptors>
    <interceptor>
        <interceptor-class>com.example.service.ExceptionInterceptor</interceptor-class>
    </interceptor>
</interceptors>
<assembly-descriptor>
    <interceptor-binding>
        <ejb-name>*</ejb-name>
        <interceptor-class>com.example.service.ExceptionInterceptor</interceptor-class>
    </interceptor-binding>
</assembly-descriptor>

As a general hint, in JSF you can also have a global exception handler which just adds a faces message. When starting with this kickoff example, you could do something like this in YourExceptionHandler#handle() method:

if (exception instanceof EntityAlreadyModifiedException) { // Unwrap if necessary.
    // Add FATAL faces message and return.
}
else {
    // Continue as usual.
}