diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/util/JvmHierarchyUtil.java b/java/java-psi-api/src/com/intellij/lang/jvm/util/JvmHierarchyUtil.java
new file mode 100644
index 000000000000..709b12ca6614
--- /dev/null
+++ b/java/java-psi-api/src/com/intellij/lang/jvm/util/JvmHierarchyUtil.java
@@ -0,0 +1,67 @@
+// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.lang.jvm.util;
+
+import com.intellij.lang.jvm.JvmClass;
+import com.intellij.openapi.progress.ProgressManager;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.Set;
+import java.util.function.Function;
+
+import static com.intellij.lang.jvm.util.JvmUtil.resolveClass;
+import static com.intellij.lang.jvm.util.JvmUtil.resolveClasses;
+
+public class JvmHierarchyUtil {
+
+ private JvmHierarchyUtil() {}
+
+ /**
+ * Traverses class tree in BFS order applying the function to each superclass.
+ *
+ * Notes:
+ *
+ * - supers term includes both classes and interfaces
+ * - the function will also be applied to the start class
+ * - if the function returns non-null result for any class then traversal is stopped and result is returned
+ * - the function is applied to each class at most once
+ * - unresolvable supertypes are skipped
+ *
+ *
+ * @param start class to start traversal from
+ * @param type of the result
+ * @return first non-null result or null
+ */
+ public static R traverseSupers(@NotNull JvmClass start, @NotNull Function super JvmClass, R> f) {
+ // TODO implement method returning Stream
+ final Queue queue = new ArrayDeque<>();
+ queue.offer(start);
+
+ final Set visited = new THashSet<>();
+ while (!queue.isEmpty()) {
+ ProgressManager.checkCanceled();
+
+ JvmClass current = queue.remove();
+ if (!visited.add(current)) continue;
+
+ R result = f.apply(current);
+ if (result != null) return result;
+
+ queueSupers(queue, current);
+ }
+
+ return null;
+ }
+
+ private static void queueSupers(@NotNull Queue queue, @NotNull JvmClass current) {
+ JvmClass superClass = resolveClass(current.getSuperClassType());
+ if (superClass != null) {
+ queue.offer(superClass);
+ }
+ for (JvmClass anInterface : resolveClasses(current.getInterfaceTypes())) {
+ queue.offer(anInterface);
+ }
+ }
+}
diff --git a/java/java-psi-api/src/com/intellij/lang/jvm/util/JvmUtil.java b/java/java-psi-api/src/com/intellij/lang/jvm/util/JvmUtil.java
new file mode 100644
index 000000000000..8438735d535c
--- /dev/null
+++ b/java/java-psi-api/src/com/intellij/lang/jvm/util/JvmUtil.java
@@ -0,0 +1,29 @@
+// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package com.intellij.lang.jvm.util;
+
+import com.intellij.lang.jvm.JvmClass;
+import com.intellij.lang.jvm.JvmTypeDeclaration;
+import com.intellij.lang.jvm.types.JvmReferenceType;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import static com.intellij.util.containers.ContainerUtil.mapNotNull;
+
+class JvmUtil {
+
+ private JvmUtil() {}
+
+ @NotNull
+ static Iterable resolveClasses(@NotNull JvmReferenceType[] types) {
+ return mapNotNull(types, JvmUtil::resolveClass);
+ }
+
+ @Contract("null -> null")
+ @Nullable
+ static JvmClass resolveClass(@Nullable JvmReferenceType type) {
+ if (type == null) return null;
+ JvmTypeDeclaration resolved = type.resolve();
+ return resolved instanceof JvmClass ? (JvmClass)resolved : null;
+ }
+}