diff --git a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/RRuntimeASTAccessImpl.java b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/RRuntimeASTAccessImpl.java
index ba76e4063f61dd90997916bd5526997cb46a21fd..08cbe18cd4f7f2278adb4d28be3c838f9d233733 100644
--- a/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/RRuntimeASTAccessImpl.java
+++ b/com.oracle.truffle.r.engine/src/com/oracle/truffle/r/engine/RRuntimeASTAccessImpl.java
@@ -59,6 +59,7 @@ import com.oracle.truffle.r.runtime.RArguments;
 import com.oracle.truffle.r.runtime.RCaller;
 import com.oracle.truffle.r.runtime.RDeparse;
 import com.oracle.truffle.r.runtime.RError;
+import com.oracle.truffle.r.runtime.RError.ShowCallerOf;
 import com.oracle.truffle.r.runtime.RRuntimeASTAccess;
 import com.oracle.truffle.r.runtime.RootWithBody;
 import com.oracle.truffle.r.runtime.Utils;
@@ -79,7 +80,9 @@ import com.oracle.truffle.r.runtime.nodes.InternalRSyntaxNodeChildren;
 import com.oracle.truffle.r.runtime.nodes.RBaseNode;
 import com.oracle.truffle.r.runtime.nodes.RInstrumentableNode;
 import com.oracle.truffle.r.runtime.nodes.RNode;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxCall;
 import com.oracle.truffle.r.runtime.nodes.RSyntaxElement;
+import com.oracle.truffle.r.runtime.nodes.RSyntaxLookup;
 import com.oracle.truffle.r.runtime.nodes.RSyntaxNode;
 
 /**
@@ -189,6 +192,32 @@ class RRuntimeASTAccessImpl implements RRuntimeASTAccess {
                 RCaller parent = RArguments.getCall(frame);
                 frame = Utils.getCallerFrame(parent, FrameAccess.READ_ONLY);
             }
+            return findCallerFromFrame(frame);
+        } else if (call instanceof ShowCallerOf) {
+            Frame frame = Utils.getActualCurrentFrame();
+
+            boolean match = false;
+            String name = ((ShowCallerOf) call).getCallerOf();
+            Frame f = frame;
+            while (f != null && RArguments.isRFrame(f)) {
+                RCaller parent = RArguments.getCall(f);
+                if (parent.isValidCaller()) {
+                    RSyntaxElement syntaxNode = parent.getSyntaxNode();
+                    if (syntaxNode instanceof RSyntaxCall) {
+                        RSyntaxElement syntaxElement = ((RSyntaxCall) syntaxNode).getSyntaxLHS();
+                        if (syntaxElement instanceof RSyntaxLookup) {
+                            if (match) {
+                                return findCallerFromFrame(f);
+                            }
+                            if (name.equals(((RSyntaxLookup) syntaxElement).getIdentifier())) {
+                                match = true;
+                            }
+                        }
+                    }
+                }
+                f = Utils.getCallerFrame(parent, FrameAccess.READ_ONLY);
+            }
+
             return findCallerFromFrame(frame);
         } else {
             RBaseNode originalCall = checkBuiltin(call);
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java
index f4147ee060e9e18ad5a43a21f381ddf23955f4c7..c89561bf38828d6f30571f0cd98efd44e3cbef83 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/BasePackage.java
@@ -108,10 +108,12 @@ import com.oracle.truffle.r.nodes.builtin.fastr.FastRIdentityNodeGen;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRInspect;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRInspectNodeGen;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRInterop;
+import com.oracle.truffle.r.nodes.builtin.fastr.FastRInterop.FastRInteropCheckException;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRInterop.FastRInteropClearException;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRInterop.FastRInteropGetException;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRInterop.FastRInteropTry;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRInteropFactory;
+import com.oracle.truffle.r.nodes.builtin.fastr.FastRInteropFactory.FastRInteropCheckExceptionNodeGen;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRInteropFactory.FastRInteropClearExceptionNodeGen;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRInteropFactory.FastRInteropGetExceptionNodeGen;
 import com.oracle.truffle.r.nodes.builtin.fastr.FastRInteropFactory.FastRInteropTryNodeGen;
@@ -449,6 +451,7 @@ public class BasePackage extends RBuiltinPackage {
         add(FastROptionBuiltin.class, FastROptionBuiltin::create);
         add(FastRTestsTry.class, FastRTestsTryNodeGen::create);
         add(FastRInteropTry.class, FastRInteropTryNodeGen::create);
+        add(FastRInteropCheckException.class, FastRInteropCheckExceptionNodeGen::create);
         add(FastRInteropGetException.class, FastRInteropGetExceptionNodeGen::create);
         add(FastRInteropClearException.class, FastRInteropClearExceptionNodeGen::create);
         add(FastRInspect.class, FastRInspectNodeGen::create);
@@ -461,8 +464,14 @@ public class BasePackage extends RBuiltinPackage {
         add(FastRInterop.DoCallExternal.class, FastRInteropFactory.DoCallExternalNodeGen::create);
         add(FastRInterop.IsExternal.class, FastRInteropFactory.IsExternalNodeGen::create);
         add(FastRInterop.JavaClass.class, FastRInteropFactory.JavaClassNodeGen::create);
+        add(FastRInterop.GetJavaClass.class, FastRInteropFactory.GetJavaClassNodeGen::create);
         add(FastRInterop.JavaClassName.class, FastRInteropFactory.JavaClassNameNodeGen::create);
         add(FastRInterop.JavaAddToClasspath.class, FastRInteropFactory.JavaAddToClasspathNodeGen::create);
+        add(FastRInterop.JavaClasspath.class, FastRInteropFactory.JavaClasspathNodeGen::create);
+        add(FastRInterop.JavaIsIdentical.class, FastRInteropFactory.JavaIsIdenticalNodeGen::create);
+        add(FastRInterop.JavaIsAssignableFrom.class, FastRInteropFactory.JavaIsAssignableFromNodeGen::create);
+        add(FastRInterop.JavaIsInstance.class, FastRInteropFactory.JavaIsInstanceNodeGen::create);
+        add(FastRInterop.JavaAsTruffleObject.class, FastRInteropFactory.JavaAsTruffleObjectNodeGen::create);
         add(FastRInterop.IsForeignArray.class, FastRInteropFactory.IsForeignArrayNodeGen::create);
         add(FastRInterop.NewJavaArray.class, FastRInteropFactory.NewJavaArrayNodeGen::create);
         add(FastRInterop.ToJavaArray.class, FastRInteropFactory.ToJavaArrayNodeGen::create);
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRInterop.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRInterop.java
index adc76dc605830af8f794d21d417b2936c10c2aa2..06f77fcdc7b2bc20cd6ea85ce106a5ee3938a822 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRInterop.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/fastr/FastRInterop.java
@@ -51,6 +51,7 @@ import com.oracle.truffle.api.dsl.Specialization;
 import com.oracle.truffle.api.frame.VirtualFrame;
 import com.oracle.truffle.api.interop.ArityException;
 import com.oracle.truffle.api.interop.ForeignAccess;
+import com.oracle.truffle.api.interop.InteropException;
 import com.oracle.truffle.api.interop.Message;
 import com.oracle.truffle.api.interop.TruffleObject;
 import com.oracle.truffle.api.interop.UnknownIdentifierException;
@@ -59,6 +60,7 @@ import com.oracle.truffle.api.interop.UnsupportedTypeException;
 import com.oracle.truffle.api.interop.java.JavaInterop;
 import com.oracle.truffle.api.nodes.DirectCallNode;
 import com.oracle.truffle.api.nodes.Node;
+import com.oracle.truffle.api.profiles.BranchProfile;
 import com.oracle.truffle.api.profiles.ConditionProfile;
 import com.oracle.truffle.api.source.Source;
 import com.oracle.truffle.api.source.Source.Builder;
@@ -478,6 +480,8 @@ public class FastRInterop {
     @RBuiltin(name = "new.java.class", visibility = ON, kind = PRIMITIVE, parameterNames = {"class", "silent"}, behavior = COMPLEX)
     public abstract static class JavaClass extends RBuiltinNode.Arg2 {
 
+        private static final Object accessError = new Object();
+
         static {
             Casts casts = new Casts(JavaClass.class);
             casts.arg("class").mustBe(stringValue()).asStringVector().mustBe(Predef.singleElement()).findFirst();
@@ -487,14 +491,81 @@ public class FastRInterop {
 
         @Specialization
         @TruffleBoundary
-        public TruffleObject javaClass(String clazz, boolean silent) {
+        public TruffleObject javaClass(TruffleObject obj, boolean silent) {
+            if (JavaInterop.isJavaObject(obj)) {
+                return JavaInterop.toJavaClass(obj);
+            }
+            throw error(RError.Message.GENERIC, "unsupported type " + obj.getClass().getName());
+        }
+
+        protected boolean isClass(Object obj) {
+            return obj != null && obj instanceof Class;
+        }
+
+        @Specialization(guards = {"isClass(clazz)", "clazz.equals(cachedClazz)"}, limit = "10")
+        public TruffleObject javaClassCached(String clazz, boolean silent,
+                        @Cached("clazz") String cachedClazz,
+                        @Cached("getJavaClass(clazz, silent)") Object result,
+                        @Cached("create()") BranchProfile interopExceptionProfile) {
+            return javaClassToTruffleObject(clazz, result, interopExceptionProfile);
+        }
+
+        @Specialization(replaces = "javaClassCached")
+        public TruffleObject javaClass(String clazz, boolean silent,
+                        @Cached("create()") BranchProfile interopExceptionProfile) {
+            Object result = getJavaClass(clazz, silent);
+            return javaClassToTruffleObject(clazz, result, interopExceptionProfile);
+        }
+
+        @TruffleBoundary
+        private TruffleObject javaClassToTruffleObject(String clazz, Object result, BranchProfile interopExceptionProfile) {
+            if (result == RNull.instance) {
+                return RNull.instance;
+            }
+            if (result instanceof Class<?>) {
+                return JavaInterop.asTruffleObject(result);
+            } else if (result == accessError) {
+                CompilerDirectives.transferToInterpreter();
+                throw error(RError.Message.GENERIC, "error while accessing Java class: " + clazz);
+            } else {
+                interopExceptionProfile.enter();
+                if (result instanceof RuntimeException) {
+                    throw RError.handleInteropException(this, (RuntimeException) result);
+                } else {
+                    assert result instanceof Throwable;
+                    throw RError.handleInteropException(this, new RuntimeException((Throwable) result));
+                }
+            }
+        }
+
+        protected static Object getJavaClass(String className, boolean silent) {
+            Class<?> clazz = getPrimitiveClass(className);
+            if (clazz != null) {
+                return clazz;
+            }
+            return loadClass(className, silent);
+        }
+
+        @TruffleBoundary
+        private static Object loadClass(String clazz, boolean silent) {
             try {
-                return JavaInterop.asTruffleObject(RContext.getInstance().loadClass(clazz.replaceAll("/", ".")));
-            } catch (ClassNotFoundException | SecurityException | IllegalArgumentException e) {
+                Class<?> result = RContext.getInstance().loadClass(clazz);
+                if (result == null) {
+                    // not found
+                    if (silent) {
+                        return RNull.instance;
+                    } else {
+                        return new ClassNotFoundException(clazz + " not found");
+                    }
+                }
+                return result;
+            } catch (SecurityException | IllegalArgumentException e) {
+                return accessError;
+            } catch (RuntimeException e) {
                 if (silent) {
                     return RNull.instance;
                 }
-                throw error(RError.Message.GENERIC, "error while accessing Java class: " + e.getMessage());
+                return e;
             }
         }
     }
@@ -529,31 +600,57 @@ public class FastRInterop {
         }
     }
 
+    @RBuiltin(name = "java.classpath", visibility = ON, kind = PRIMITIVE, parameterNames = {}, behavior = COMPLEX)
+    public abstract static class JavaClasspath extends RBuiltinNode.Arg0 {
+
+        static {
+            Casts.noCasts(JavaClasspath.class);
+        }
+
+        @Specialization
+        @TruffleBoundary
+        public RAbstractStringVector getEntries() {
+            RContext ctx = RContext.getInstance();
+            String[] paths = ctx.getInteropClasspathEntries();
+            return RDataFactory.createStringVector(paths, true);
+        }
+    }
+
     @ImportStatic({RRuntime.class})
-    @RBuiltin(name = "java.class", visibility = ON, kind = PRIMITIVE, parameterNames = {"class"}, behavior = COMPLEX)
-    public abstract static class JavaClassName extends RBuiltinNode.Arg1 {
+    @RBuiltin(name = "java.class", visibility = ON, kind = PRIMITIVE, parameterNames = {"obj", "getClassName"}, behavior = COMPLEX)
+    public abstract static class JavaClassName extends RBuiltinNode.Arg2 {
 
         static {
-            Casts.noCasts(JavaClassName.class);
+            Casts casts = new Casts(JavaClassName.class);
+            casts.arg("getClassName").mapMissing(Predef.constant(RRuntime.LOGICAL_FALSE)).mustBe(logicalValue().or(Predef.nullValue())).asLogicalVector().mustBe(singleElement()).findFirst().mustBe(
+                            notLogicalNA()).map(Predef.toBoolean());
         }
 
         @Specialization(guards = {"isJavaObject(obj)"})
         @TruffleBoundary
-        public Object javaClassName(TruffleObject obj) {
-            Object o = JavaInterop.asJavaObject(Object.class, obj);
-            if (o == null) {
-                return RNull.instance;
+        public Object javaClassName(Object obj, boolean getClassName) {
+            if (isJavaObject(obj)) {
+                Object o = JavaInterop.asJavaObject(Object.class, (TruffleObject) obj);
+                if (o == null) {
+                    return RNull.instance;
+                }
+                if (getClassName && o instanceof Class) {
+                    return ((Class<?>) o).getName();
+                }
+                return o.getClass().getName();
+            } else {
+                throw error(RError.Message.GENERIC, "unsupported type " + obj.getClass().getName());
             }
-            return o.getClass().getName();
         }
 
-        protected boolean isJavaObject(TruffleObject obj) {
-            return JavaInterop.isJavaObject(obj);
+        protected boolean isJavaObject(Object obj) {
+            return RRuntime.isForeignObject(obj) && JavaInterop.isJavaObject(obj);
         }
 
         @Fallback
-        public String javaClassName(@SuppressWarnings("unused") Object obj) {
-            throw error(RError.Message.GENERIC, "unsupported type");
+        public String javaClassName(@SuppressWarnings("unused") Object obj,
+                        @SuppressWarnings("unused") Object getClassName) {
+            throw error(RError.Message.GENERIC, "unsupported type " + obj.getClass().getName());
         }
     }
 
@@ -566,7 +663,6 @@ public class FastRInterop {
         }
 
         @Specialization(guards = {"isForeignObject(obj)"})
-        @TruffleBoundary
         public byte isArray(TruffleObject obj,
                         @Cached("HAS_SIZE.createNode()") Node hasSize) {
             return RRuntime.asLogical(ForeignAccess.sendHasSize(hasSize, obj));
@@ -578,6 +674,194 @@ public class FastRInterop {
         }
     }
 
+    @RBuiltin(name = ".fastr.interop.getJavaClass", visibility = ON, kind = PRIMITIVE, parameterNames = {"obj"}, behavior = COMPLEX)
+    public abstract static class GetJavaClass extends RBuiltinNode.Arg1 {
+
+        static {
+            Casts.noCasts(GetJavaClass.class);
+        }
+
+        @Specialization
+        @TruffleBoundary
+        public TruffleObject javaClass(TruffleObject obj) {
+            if (JavaInterop.isJavaObject(obj)) {
+                return JavaInterop.toJavaClass(obj);
+            }
+            throw error(RError.Message.GENERIC, "unsupported type " + obj.getClass().getName());
+        }
+    }
+
+    @ImportStatic({Message.class, RRuntime.class})
+    @RBuiltin(name = ".fastr.interop.isIdentical", visibility = ON, kind = PRIMITIVE, parameterNames = {"x1", "x2"}, behavior = COMPLEX)
+    public abstract static class JavaIsIdentical extends RBuiltinNode.Arg2 {
+
+        static {
+            Casts.noCasts(JavaIsIdentical.class);
+        }
+
+        @Specialization(guards = {"isJavaObject(x1)", "isJavaObject(x2)"})
+        public byte isIdentical(TruffleObject x1, TruffleObject x2) {
+            return RRuntime.asLogical(JavaInterop.asJavaObject(Object.class, x1) == JavaInterop.asJavaObject(Object.class, x2));
+        }
+
+        @Fallback
+        @TruffleBoundary
+        public byte isIdentical(@SuppressWarnings("unused") Object x1, @SuppressWarnings("unused") Object x2) {
+            throw error(RError.Message.GENERIC, String.format("unsupported types: %s, %s", x1.getClass().getName(), x2.getClass().getName()));
+        }
+
+        protected boolean isJavaObject(TruffleObject obj) {
+            return JavaInterop.isJavaObject(obj);
+        }
+    }
+
+    @ImportStatic({Message.class, RRuntime.class})
+    @RBuiltin(name = ".fastr.interop.asJavaTruffleObject", visibility = ON, kind = PRIMITIVE, parameterNames = {"x"}, behavior = COMPLEX)
+    public abstract static class JavaAsTruffleObject extends RBuiltinNode.Arg1 {
+
+        static {
+            Casts.noCasts(JavaAsTruffleObject.class);
+        }
+
+        @Specialization
+        public TruffleObject asTruffleObject(byte b) {
+            return JavaInterop.asTruffleObject(RRuntime.fromLogical(b));
+        }
+
+        @Specialization
+        public TruffleObject asTruffleObject(int i) {
+            return JavaInterop.asTruffleObject(i);
+        }
+
+        @Specialization
+        public TruffleObject asTruffleObject(double d) {
+            return JavaInterop.asTruffleObject(d);
+        }
+
+        @Specialization
+        public TruffleObject asTruffleObject(String s) {
+            return JavaInterop.asTruffleObject(s);
+        }
+
+        @Specialization
+        public TruffleObject asTruffleObject(RInteropByte b) {
+            return JavaInterop.asTruffleObject(b.getValue());
+        }
+
+        @Specialization
+        public TruffleObject asTruffleObject(RInteropChar c) {
+            return JavaInterop.asTruffleObject(c.getValue());
+        }
+
+        @Specialization
+        public TruffleObject asTruffleObject(RInteropFloat f) {
+            return JavaInterop.asTruffleObject(f.getValue());
+        }
+
+        @Specialization
+        public TruffleObject asTruffleObject(RInteropLong l) {
+            return JavaInterop.asTruffleObject(l.getValue());
+        }
+
+        @Specialization
+        public TruffleObject asTruffleObject(RInteropShort s) {
+            return JavaInterop.asTruffleObject(s.getValue());
+        }
+
+        @Fallback
+        @TruffleBoundary
+        public byte asTruffleObject(@SuppressWarnings("unused") Object x) {
+            throw error(RError.Message.GENERIC, String.format("unsupported type: %s", x.getClass().getName()));
+        }
+    }
+
+    @ImportStatic({Message.class, RRuntime.class})
+    @RBuiltin(name = ".fastr.interop.isAssignableFrom", visibility = ON, kind = PRIMITIVE, parameterNames = {"x1", "x2"}, behavior = COMPLEX)
+    public abstract static class JavaIsAssignableFrom extends RBuiltinNode.Arg2 {
+
+        static {
+            Casts.noCasts(JavaIsAssignableFrom.class);
+        }
+
+        @Specialization(guards = {"isJavaObject(x1)", "isJavaObject(x2)"})
+        public byte isAssignable(TruffleObject x1, TruffleObject x2) {
+            Object jo1 = JavaInterop.asJavaObject(Object.class, x1);
+            Class<?> cl1 = (jo1 instanceof Class) ? (Class<?>) jo1 : jo1.getClass();
+            Object jo2 = JavaInterop.asJavaObject(Object.class, x2);
+            Class<?> cl2 = (jo2 instanceof Class) ? (Class<?>) jo2 : jo2.getClass();
+            return RRuntime.asLogical(cl2.isAssignableFrom(cl1));
+        }
+
+        @Fallback
+        @TruffleBoundary
+        public byte isAssignable(@SuppressWarnings("unused") Object x1, @SuppressWarnings("unused") Object x2) {
+            throw error(RError.Message.GENERIC, String.format("unsupported types: %s, %s", x1.getClass().getName(), x2.getClass().getName()));
+        }
+
+        protected boolean isJavaObject(TruffleObject obj) {
+            return JavaInterop.isJavaObject(obj);
+        }
+    }
+
+    @ImportStatic({Message.class, RRuntime.class})
+    @RBuiltin(name = ".fastr.interop.isInstance", visibility = ON, kind = PRIMITIVE, parameterNames = {"x1", "x2"}, behavior = COMPLEX)
+    public abstract static class JavaIsInstance extends RBuiltinNode.Arg2 {
+
+        static {
+            Casts.noCasts(JavaIsInstance.class);
+        }
+
+        @Specialization(guards = {"isJavaObject(x1)", "isJavaObject(x2)"})
+        public byte isInstance(TruffleObject x1, TruffleObject x2) {
+            Object jo1 = JavaInterop.asJavaObject(Object.class, x1);
+            Object jo2 = JavaInterop.asJavaObject(Object.class, x2);
+            if (jo1 instanceof Class) {
+                Class<?> cl1 = (Class<?>) jo1;
+                return RRuntime.asLogical(cl1.isInstance(jo2));
+            }
+            return RRuntime.asLogical(jo1.getClass().isInstance(jo2));
+        }
+
+        @Specialization(guards = {"isJavaObject(x1)"})
+        public byte isInstance(TruffleObject x1, RInteropScalar x2,
+                        @Cached("createR2Foreign()") R2Foreign r2Foreign) {
+            Object jo1 = JavaInterop.asJavaObject(Object.class, x1);
+            if (jo1 instanceof Class) {
+                Class<?> cl1 = (Class<?>) jo1;
+                return RRuntime.asLogical(cl1.isInstance(r2Foreign.execute(x2)));
+            }
+            return RRuntime.asLogical(jo1.getClass().isInstance(x2));
+        }
+
+        @Specialization(guards = {"isJavaObject(x1)", "!isJavaObject(x2)", "!isInterop(x2)"})
+        public byte isInstance(TruffleObject x1, Object x2) {
+            Object jo1 = JavaInterop.asJavaObject(Object.class, x1);
+            if (jo1 instanceof Class) {
+                Class<?> cl1 = (Class<?>) jo1;
+                return RRuntime.asLogical(cl1.isInstance(x2));
+            }
+            return RRuntime.asLogical(jo1.getClass().isInstance(x2));
+        }
+
+        @Fallback
+        @TruffleBoundary
+        public byte isInstance(@SuppressWarnings("unused") Object x1, @SuppressWarnings("unused") Object x2) {
+            throw error(RError.Message.GENERIC, String.format("unsupported types: %s, %s", x1.getClass().getName(), x2.getClass().getName()));
+        }
+
+        protected boolean isJavaObject(Object obj) {
+            return RRuntime.isForeignObject(obj) && JavaInterop.isJavaObject((TruffleObject) obj);
+        }
+
+        protected boolean isInterop(Object obj) {
+            return obj instanceof RInteropScalar;
+        }
+
+        protected R2Foreign createR2Foreign() {
+            return R2ForeignNodeGen.create();
+        }
+    }
+
     @RBuiltin(name = "new.java.array", visibility = ON, kind = PRIMITIVE, parameterNames = {"class", "dim"}, behavior = COMPLEX)
     public abstract static class NewJavaArray extends RBuiltinNode.Arg2 {
 
@@ -604,11 +888,11 @@ public class FastRInterop {
         }
 
         private Class<?> getClazz(String className) throws RError {
-            try {
-                return classForName(className);
-            } catch (ClassNotFoundException e) {
-                throw error(RError.Message.GENERIC, "error while accessing Java class: " + e.getMessage());
+            Class<?> result = classForName(className);
+            if (result == null) {
+                throw error(RError.Message.GENERIC, "cannot access Java class %s", className);
             }
+            return result;
         }
     }
 
@@ -648,7 +932,23 @@ public class FastRInterop {
         @Specialization
         @TruffleBoundary
         public Object toArray(RAbstractIntVector vec, String className, boolean flat) {
-            return toArray(vec, flat, getClazz(className), (array, i) -> Array.set(array, i, vec.getDataAt(i)));
+            return toArray(vec, flat, getClazz(className), (array, i) -> {
+                if (Byte.TYPE.getName().equals(className)) {
+                    Array.set(array, i, (byte) vec.getDataAt(i));
+                } else if (Character.TYPE.getName().equals(className)) {
+                    Array.set(array, i, (char) vec.getDataAt(i));
+                } else if (Double.TYPE.getName().equals(className)) {
+                    Array.set(array, i, (double) vec.getDataAt(i));
+                } else if (Float.TYPE.getName().equals(className)) {
+                    Array.set(array, i, (float) vec.getDataAt(i));
+                } else if (Long.TYPE.getName().equals(className)) {
+                    Array.set(array, i, (long) vec.getDataAt(i));
+                } else if (Short.TYPE.getName().equals(className)) {
+                    Array.set(array, i, (short) vec.getDataAt(i));
+                } else {
+                    Array.set(array, i, vec.getDataAt(i));
+                }
+            });
         }
 
         @Specialization
@@ -660,7 +960,23 @@ public class FastRInterop {
         @Specialization
         @TruffleBoundary
         public Object toArray(RAbstractDoubleVector vec, String className, boolean flat) {
-            return toArray(vec, flat, getClazz(className), (array, i) -> Array.set(array, i, vec.getDataAt(i)));
+            return toArray(vec, flat, getClazz(className), (array, i) -> {
+                if (Byte.TYPE.getName().equals(className)) {
+                    Array.set(array, i, (byte) vec.getDataAt(i));
+                } else if (Character.TYPE.getName().equals(className)) {
+                    Array.set(array, i, (char) vec.getDataAt(i));
+                } else if (Float.TYPE.getName().equals(className)) {
+                    Array.set(array, i, (float) vec.getDataAt(i));
+                } else if (Integer.TYPE.getName().equals(className)) {
+                    Array.set(array, i, (int) vec.getDataAt(i));
+                } else if (Long.TYPE.getName().equals(className)) {
+                    Array.set(array, i, (long) vec.getDataAt(i));
+                } else if (Short.TYPE.getName().equals(className)) {
+                    Array.set(array, i, (short) vec.getDataAt(i));
+                } else {
+                    Array.set(array, i, vec.getDataAt(i));
+                }
+            });
         }
 
         @Specialization
@@ -691,14 +1007,14 @@ public class FastRInterop {
         @TruffleBoundary
         public Object toArray(RAbstractVector vec, @SuppressWarnings("unused") RMissing className, boolean flat,
                         @Cached("createR2Foreign()") R2Foreign r2Foreign) {
-            return toArray(vec, flat, Object.class, (array, i) -> Array.set(array, i, r2Foreign.execute(vec.getDataAtAsObject(i))));
+            return toArray(vec, flat, Object.class, r2Foreign);
         }
 
         @Specialization(guards = "!isJavaLikeVector(vec)")
         @TruffleBoundary
         public Object toArray(RAbstractVector vec, String className, boolean flat,
                         @Cached("createR2Foreign()") R2Foreign r2Foreign) {
-            return toArray(vec, flat, getClazz(className), (array, i) -> Array.set(array, i, r2Foreign.execute(vec.getDataAtAsObject(i))));
+            return toArray(vec, flat, getClazz(className), r2Foreign);
         }
 
         @Specialization
@@ -747,6 +1063,26 @@ public class FastRInterop {
             return JavaInterop.asTruffleObject(array);
         }
 
+        private Object toArray(RAbstractVector vec, boolean flat, Class<?> clazz, R2Foreign r2Foreign) throws IllegalArgumentException, ArrayIndexOutOfBoundsException {
+            int[] dims = getDim(flat, vec);
+            final Object array = Array.newInstance(clazz, dims);
+            TruffleObject truffleArray = JavaInterop.asTruffleObject(array);
+
+            for (int d = 0; d < dims.length; d++) {
+                int dim = dims[d];
+                // TODO works only for flat
+                for (int i = 0; i < dim; i++) {
+                    try {
+                        Object value = r2Foreign.execute(vec.getDataAtAsObject(i));
+                        ForeignAccess.sendWrite(Message.WRITE.createNode(), truffleArray, i, value);
+                    } catch (InteropException ex) {
+                        throw error(RError.Message.GENERIC, ex.getMessage());
+                    }
+                }
+            }
+            return truffleArray;
+        }
+
         private interface VecElementToArray {
             void toArray(Object array, Integer i);
         }
@@ -781,11 +1117,11 @@ public class FastRInterop {
         }
 
         private Class<?> getClazz(String className) throws RError {
-            try {
-                return classForName(className);
-            } catch (ClassNotFoundException e) {
-                throw error(RError.Message.GENERIC, "error while accessing Java class: " + e.getMessage());
+            Class<?> result = classForName(className);
+            if (result == null) {
+                throw error(RError.Message.GENERIC, "cannot access Java class %s", className);
             }
+            return result;
         }
 
         protected boolean isJavaObject(TruffleObject obj) {
@@ -802,30 +1138,32 @@ public class FastRInterop {
     }
 
     @ImportStatic({Message.class, RRuntime.class})
-    @RBuiltin(name = ".fastr.interop.fromArray", visibility = ON, kind = PRIMITIVE, parameterNames = {"array"}, behavior = COMPLEX)
-    public abstract static class FromForeignArray extends RBuiltinNode.Arg1 {
+    @RBuiltin(name = ".fastr.interop.fromArray", visibility = ON, kind = PRIMITIVE, parameterNames = {"array", "recursive"}, behavior = COMPLEX)
+    public abstract static class FromForeignArray extends RBuiltinNode.Arg2 {
 
         static {
             Casts casts = new Casts(FromForeignArray.class);
             casts.arg("array").castForeignObjects(false).mustNotBeMissing();
+            casts.arg("recursive").mapMissing(Predef.constant(RRuntime.LOGICAL_FALSE)).mustBe(logicalValue().or(Predef.nullValue())).asLogicalVector().mustBe(singleElement()).findFirst().mustBe(
+                            notLogicalNA()).map(Predef.toBoolean());
         }
 
         private final ConditionProfile isArrayProfile = ConditionProfile.createBinaryProfile();
 
         @Specialization(guards = {"isForeignObject(obj)"})
         @TruffleBoundary
-        public Object fromArray(TruffleObject obj,
+        public Object fromArray(TruffleObject obj, boolean recursive,
                         @Cached("HAS_SIZE.createNode()") Node hasSize,
                         @Cached("create()") ForeignArray2R array2R) {
             if (isArrayProfile.profile(ForeignAccess.sendHasSize(hasSize, obj))) {
-                return array2R.convert(obj);
+                return array2R.convert(obj, recursive);
             } else {
                 throw error(RError.Message.GENERIC, "not a java array");
             }
         }
 
         @Fallback
-        public Object fromObject(@SuppressWarnings("unused") Object obj) {
+        public Object fromObject(@SuppressWarnings("unused") Object obj, @SuppressWarnings("unused") Object recursive) {
             throw error(RError.Message.GENERIC, "not a java array");
         }
     }
@@ -884,32 +1222,33 @@ public class FastRInterop {
         }
     }
 
-    private static Class<?> classForName(String className) throws ClassNotFoundException {
-        if (className.equals(Byte.TYPE.getName())) {
-            return Byte.TYPE;
+    private static Class<?> classForName(String className) {
+        Class<?> clazz = getPrimitiveClass(className);
+        if (clazz != null) {
+            return clazz;
         }
-        if (className.equals(Boolean.TYPE.getName())) {
+        return RContext.getInstance().loadClass(className);
+    }
+
+    private static Class<?> getPrimitiveClass(String className) {
+        if (Boolean.TYPE.getName().equals(className)) {
             return Boolean.TYPE;
-        }
-        if (className.equals(Character.TYPE.getName())) {
+        } else if (Byte.TYPE.getName().equals(className)) {
+            return Byte.TYPE;
+        } else if (Character.TYPE.getName().equals(className)) {
             return Character.TYPE;
-        }
-        if (className.equals(Double.TYPE.getName())) {
+        } else if (Double.TYPE.getName().equals(className)) {
             return Double.TYPE;
-        }
-        if (className.equals(Float.TYPE.getName())) {
+        } else if (Float.TYPE.getName().equals(className)) {
             return Float.TYPE;
-        }
-        if (className.equals(Integer.TYPE.getName())) {
+        } else if (Integer.TYPE.getName().equals(className)) {
             return Integer.TYPE;
-        }
-        if (className.equals(Long.TYPE.getName())) {
+        } else if (Long.TYPE.getName().equals(className)) {
             return Long.TYPE;
-        }
-        if (className.equals(Short.TYPE.getName())) {
+        } else if (Short.TYPE.getName().equals(className)) {
             return Short.TYPE;
         }
-        return Class.forName(className);
+        return null;
     }
 
     @RBuiltin(name = "do.call.external", visibility = ON, kind = PRIMITIVE, parameterNames = {"receiver", "what", "args"}, behavior = COMPLEX)
@@ -970,16 +1309,15 @@ public class FastRInterop {
 
         @Specialization
         public Object tryFunc(VirtualFrame frame, RFunction function, byte check) {
-            boolean isCheck = RRuntime.fromLogical(check);
             getInteropTryState().stepIn();
             try {
                 return call.call(frame, function, RArgsValuesAndNames.EMPTY);
             } catch (FastRInteropTryException e) {
-                Throwable cause = e.getCause();
                 CompilerDirectives.transferToInterpreter();
-                if (cause instanceof TruffleException) {
+                Throwable cause = e.getCause();
+                if (cause instanceof TruffleException || cause.getCause() instanceof ClassNotFoundException) {
                     cause = cause.getCause();
-                    if (isCheck) {
+                    if (RRuntime.fromLogical(check)) {
                         String causeName = cause.getClass().getName();
                         String msg = cause.getMessage();
                         msg = msg != null ? String.format("%s: %s", causeName, msg) : causeName;
@@ -998,6 +1336,40 @@ public class FastRInterop {
 
     }
 
+    @RBuiltin(name = ".fastr.interop.checkException", kind = PRIMITIVE, parameterNames = {"silent", "showCallerOf"}, behavior = COMPLEX)
+    public abstract static class FastRInteropCheckException extends RBuiltinNode.Arg2 {
+        static {
+            Casts casts = new Casts(FastRInteropCheckException.class);
+            casts.arg("silent").mustBe(logicalValue()).asLogicalVector().mustBe(singleElement()).findFirst();
+            casts.arg("showCallerOf").allowMissing().mustBe(stringValue()).asStringVector().mustBe(singleElement()).findFirst();
+        }
+
+        @Specialization
+        public Object getException(VirtualFrame frame, byte silent, RMissing callerFor) {
+            return getException(frame, silent, (String) null);
+        }
+
+        @Specialization
+        public Object getException(VirtualFrame frame, byte silent, String showCallerOf) {
+            Throwable t = getInteropTryState().lastException;
+            if (t != null) {
+                CompilerDirectives.transferToInterpreter();
+                getInteropTryState().lastException = null;
+                if (!RRuntime.fromLogical(silent)) {
+                    String causeName = t.getClass().getName();
+                    String msg = t.getMessage();
+                    msg = msg != null ? String.format("%s: %s", causeName, msg) : causeName;
+                    if (showCallerOf == null) {
+                        throw RError.error(RError.SHOW_CALLER, RError.Message.GENERIC, msg);
+                    } else {
+                        throw RError.error(new RError.ShowCallerOf(showCallerOf), RError.Message.GENERIC, msg);
+                    }
+                }
+            }
+            return RNull.instance;
+        }
+    }
+
     @RBuiltin(name = ".fastr.interop.getTryException", kind = PRIMITIVE, parameterNames = {"clear"}, behavior = COMPLEX)
     public abstract static class FastRInteropGetException extends RBuiltinNode.Arg1 {
         static {
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
index a4cc7250eb6f3bc3bf9fd94fa9592e862351bd29..9dc2b82baa6afd0b96a0aa0030aa38e4efce9cd1 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RError.java
@@ -123,6 +123,23 @@ public final class RError extends RuntimeException implements TruffleException {
      */
     public static final ErrorContext SHOW_CALLER = new ErrorContextImpl();
 
+    /**
+     * This flags a call to {@code error} or {@code warning} where the error message should show the
+     * caller provided function if such is available. Otherwise the caller of the current function
+     * will be shown.
+     */
+    public static final class ShowCallerOf extends ErrorContext {
+        private final String function;
+
+        public ShowCallerOf(String function) {
+            this.function = function;
+        }
+
+        public String getCallerOf() {
+            return function;
+        }
+    }
+
     /**
      * A very special case that ensures that no caller is output in the error/warning message. This
      * is needed where, even if there is a caller, GnuR does not show it.
@@ -193,7 +210,7 @@ public final class RError extends RuntimeException implements TruffleException {
 
     @TruffleBoundary
     public static RError handleInteropException(Node node, RuntimeException e) {
-        if (e instanceof TruffleException) {
+        if (e instanceof TruffleException || e.getCause() instanceof ClassNotFoundException) {
             if (RContext.getInstance().stateInteropTry.isInTry()) {
                 // will be catched and handled in .fastr.interop.try builtin
                 throw new FastRInteropTryException(e);
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java
index 14cbbbc8216169c4ca2e8770410c69787fec6a70..bd968fce28658fa6a8960386888877b6d8a88099 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/context/RContext.java
@@ -828,11 +828,29 @@ public final class RContext {
         }
     };
 
-    public Class<?> loadClass(String className) throws ClassNotFoundException {
-        if (FastRConfig.InternalGridAwtSupport) {
-            return interopClassLoader.loadClass(className);
+    private final HashMap<String, Class<?>> classCache = new HashMap<>();
+
+    /**
+     * This function also takes class names with '/' instead of '.'. A null return value means that
+     * the class was not found.
+     */
+    public Class<?> loadClass(String className) {
+        if (classCache.containsKey(className)) {
+            return classCache.get(className);
         }
-        return Class.forName(className);
+        String demangled = className.replaceAll("/", ".");
+        Class<?> result;
+        try {
+            if (FastRConfig.InternalGridAwtSupport) {
+                result = interopClassLoader.loadClass(demangled);
+            } else {
+                result = Class.forName(demangled);
+            }
+        } catch (ClassNotFoundException e) {
+            result = null;
+        }
+        classCache.put(className, result);
+        return result;
     }
 
     /**
@@ -849,6 +867,29 @@ public final class RContext {
             urls[i] = Paths.get(entries[i]).toUri().toURL();
         }
         interopClassLoader = URLClassLoader.newInstance(urls, interopClassLoader);
+        classCache.clear();
+    }
+
+    /**
+     * Returns all entries in the Java interop class loader.
+     * 
+     * @return the CP entries
+     */
+    public String[] getInteropClasspathEntries() {
+        if (!FastRConfig.InternalGridAwtSupport) {
+            throw RError.error(RError.NO_CALLER, RError.Message.GENERIC, "Custom class loading not supported.");
+        }
+        if (interopClassLoader instanceof URLClassLoader) {
+            URL[] urls = ((URLClassLoader) interopClassLoader).getURLs();
+            if (urls != null) {
+                String[] ret = new String[urls.length];
+                for (int i = 0; i < urls.length; i++) {
+                    ret[i] = urls[i].getPath();
+                }
+                return ret;
+            }
+        }
+        return new String[0];
     }
 
     public final class ConsoleIO {
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/interop/ForeignArray2R.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/interop/ForeignArray2R.java
index 2cd826b8689a3a24251aa967c053f58ca022fa80..e3ca6bcac927d0cf283afb8b0fb05dc11fd8f33f 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/interop/ForeignArray2R.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/interop/ForeignArray2R.java
@@ -267,7 +267,6 @@ public abstract class ForeignArray2R extends RBaseNode {
         int size = arrayData.elements.size();
 
         int[] dims = arrayData.dims != null && arrayData.dims.size() > 1 ? arrayData.dims.stream().mapToInt((i) -> i.intValue()).toArray() : null;
-        assert dims == null || sizeByDims(dims) == size : sizeByDims(dims) + " " + size;
 
         switch (type) {
             case NONE:
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
index 4c72153998acf3103c52b7ba99ba17c9eaba3104..5c8a452f4616911141ce1e0750cf3d2921841353 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/ExpectedTestOutput.test
@@ -148283,16 +148283,18 @@ NULL
 [1] "com.oracle.truffle.r.test.library.fastr.TestJavaInterop$TestClass"
 
 ##com.oracle.truffle.r.test.library.fastr.TestJavaInterop.testGetClass#
-#if (!any(R.version$engine == "FastR")) { cat('Error in java.class(1) : unsupported type', '<<<NEWLINE>>>') } else { java.class(1) }
-Error in java.class(1) : unsupported type
+#if (!any(R.version$engine == "FastR")) { cat('Error in java.class(1) : unsupported type java.lang.Double', '<<<NEWLINE>>>') } else { java.class(1) }
+Error in java.class(1) : unsupported type java.lang.Double
 
 ##com.oracle.truffle.r.test.library.fastr.TestJavaInterop.testGetClass#
-#if (!any(R.version$engine == "FastR")) { cat('Error in java.class(NULL) : unsupported type', '<<<NEWLINE>>>') } else { java.class(NULL) }
-Error in java.class(NULL) : unsupported type
+#if (!any(R.version$engine == "FastR")) { cat('Error in java.class(NULL) :', '<<<NEWLINE>>>', ' unsupported type com.oracle.truffle.r.runtime.data.RNull', '<<<NEWLINE>>>') } else { java.class(NULL) }
+Error in java.class(NULL) :
+  unsupported type com.oracle.truffle.r.runtime.data.RNull
 
 ##com.oracle.truffle.r.test.library.fastr.TestJavaInterop.testGetClass#
-#if (!any(R.version$engine == "FastR")) { cat('Error in java.class(to$methodReturnsNull()) : unsupported type', '<<<NEWLINE>>>') } else { to <- new.external(new.java.class('com.oracle.truffle.r.test.library.fastr.TestJavaInterop$TestClass'));java.class(to$methodReturnsNull()) }
-Error in java.class(to$methodReturnsNull()) : unsupported type
+#if (!any(R.version$engine == "FastR")) { cat('Error in java.class(to$methodReturnsNull()) :', '<<<NEWLINE>>>', ' unsupported type com.oracle.truffle.r.runtime.data.RNull', '<<<NEWLINE>>>') } else { to <- new.external(new.java.class('com.oracle.truffle.r.test.library.fastr.TestJavaInterop$TestClass'));java.class(to$methodReturnsNull()) }
+Error in java.class(to$methodReturnsNull()) :
+  unsupported type com.oracle.truffle.r.runtime.data.RNull
 
 ##com.oracle.truffle.r.test.library.fastr.TestJavaInterop.testIdentical#
 #if (!any(R.version$engine == "FastR")) { FALSE } else { b1 <- as.external.byte(1); b2 <- as.external.byte(1); identical(b1, b2) }
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/fastr/TestJavaInterop.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/fastr/TestJavaInterop.java
index a48f99ef1321e44a58d58491625b9420d9f1818e..c9436739bc2e8270d04065925f01259a5a223d19 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/fastr/TestJavaInterop.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/library/fastr/TestJavaInterop.java
@@ -247,9 +247,9 @@ public class TestJavaInterop extends TestBase {
     public void testGetClass() {
         assertEvalFastR(CREATE_TRUFFLE_OBJECT + "java.class(to)", "'com.oracle.truffle.r.test.library.fastr.TestJavaInterop$TestClass'");
 
-        assertEvalFastR(CREATE_TRUFFLE_OBJECT + "java.class(to$methodReturnsNull())", errorIn("java.class(to$methodReturnsNull())", "unsupported type"));
-        assertEvalFastR("java.class(NULL)", errorIn("java.class(NULL)", "unsupported type"));
-        assertEvalFastR("java.class(1)", errorIn("java.class(1)", "unsupported type"));
+        assertEvalFastR(CREATE_TRUFFLE_OBJECT + "java.class(to$methodReturnsNull())", errorIn("java.class(to$methodReturnsNull())", "unsupported type com.oracle.truffle.r.runtime.data.RNull"));
+        assertEvalFastR("java.class(NULL)", errorIn("java.class(NULL)", "unsupported type com.oracle.truffle.r.runtime.data.RNull"));
+        assertEvalFastR("java.class(1)", errorIn("java.class(1)", "unsupported type java.lang.Double"));
     }
 
     @Test