Repository: checkstyle/checkstyle

Problem statement:
Remove properties related to class loading from JavadocMethod 
part of #7116 .

We need to remove from Check any attempt to use classloader to validate source code.
Checkstyle cannot make this reliably and it is whole tool limitation for now - https://checkstyle.org/writingchecks.html#Limitations

**Migration Notes:**
properties `allowMissingThrowsTags`, `allowUndeclaredRTE`  and `allowThrowsTagsForSubclasses`, `logLoadErrors`, `suppressLoadErrors` should be just removed from configs. 
Behavior will change as there will be less violations in code, such properties did not work well so their removal should be ok, as there will be less false-positives. **Throws will not be validated by default !!** 
If you need to validate `@throws` please enable property `validateThrows` but please read documentation as there some limitations - https://checkstyle.org/config_javadoc.html#JavadocMethod .
Old documentation is at https://checkstyle.sourceforge.io/version/8.27/config_javadoc.html#JavadocMethod


Hints:

Patch:
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/PropertiesExpander.java b/src/main/java/com/puppycrawl/tools/checkstyle/PropertiesExpander.java
--- a/src/main/java/com/puppycrawl/tools/checkstyle/PropertiesExpander.java
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/PropertiesExpander.java
@@ -39,7 +39,7 @@ public final class PropertiesExpander
      * Creates a new PropertiesExpander.
      * @param properties the underlying properties to use for
      *     property resolution.
-     * @throws IllegalArgumentException indicates null was passed
+     * @throws IllegalArgumentException when properties argument is null
      */
     public PropertiesExpander(Properties properties) {
         if (properties == null) {
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/ClassResolver.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/ClassResolver.java
deleted file mode 100644
--- a/src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/ClassResolver.java
+++ /dev/null
@@ -1,244 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// checkstyle: Checks Java source code for adherence to a set of rules.
-// Copyright (C) 2001-2019 the original author or authors.
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-////////////////////////////////////////////////////////////////////////////////
-
-package com.puppycrawl.tools.checkstyle.checks.javadoc;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Utility class to resolve a class name to an actual class. Note that loaded
- * classes are not initialized.
- * <p>Limitations: this does not handle inner classes very well.</p>
- *
- */
-public class ClassResolver {
-
-    /** Period literal. */
-    private static final String PERIOD = ".";
-    /** Dollar sign literal. */
-    private static final String DOLLAR_SIGN = "$";
-
-    /** Name of the package to check if the class belongs to. **/
-    private final String pkg;
-    /** Set of imports to check against. **/
-    private final Set<String> imports;
-    /** Use to load classes. **/
-    private final ClassLoader loader;
-
-    /**
-     * Creates a new {@code ClassResolver} instance.
-     *
-     * @param loader the ClassLoader to load classes with.
-     * @param pkg the name of the package the class may belong to
-     * @param imports set of imports to check if the class belongs to
-     */
-    public ClassResolver(ClassLoader loader, String pkg, Set<String> imports) {
-        this.loader = loader;
-        this.pkg = pkg;
-        this.imports = new HashSet<>(imports);
-        this.imports.add("java.lang.*");
-    }
-
-    /**
-     * Attempts to resolve the Class for a specified name. The algorithm is
-     * to check:
-     * - fully qualified name
-     * - explicit imports
-     * - enclosing package
-     * - star imports
-     * @param name name of the class to resolve
-     * @param currentClass name of current class (for inner classes).
-     * @return the resolved class
-     * @throws ClassNotFoundException if unable to resolve the class
-     */
-    // -@cs[ForbidWildcardAsReturnType] This method can return any type, so no way to avoid wildcard
-    public Class<?> resolve(String name, String currentClass)
-            throws ClassNotFoundException {
-        // See if the class is full qualified
-        Class<?> clazz = resolveQualifiedName(name);
-        if (clazz == null) {
-            // try matching explicit imports
-            clazz = resolveMatchingExplicitImport(name);
-
-            if (clazz == null) {
-                // See if in the package
-                clazz = resolveInPackage(name);
-
-                if (clazz == null) {
-                    // see if inner class of this class
-                    clazz = resolveInnerClass(name, currentClass);
-
-                    if (clazz == null) {
-                        clazz = resolveByStarImports(name);
-                        // -@cs[NestedIfDepth] it is better to have single return point from method
-                        if (clazz == null) {
-                            throw new ClassNotFoundException(name);
-                        }
-                    }
-                }
-            }
-        }
-        return clazz;
-    }
-
-    /**
-     * Try to find class by search in package.
-     * @param name class name
-     * @return class object
-     */
-    private Class<?> resolveInPackage(String name) {
-        Class<?> clazz = null;
-        if (pkg != null && !pkg.isEmpty()) {
-            final Class<?> classFromQualifiedName = resolveQualifiedName(pkg + PERIOD + name);
-            if (classFromQualifiedName != null) {
-                clazz = classFromQualifiedName;
-            }
-        }
-        return clazz;
-    }
-
-    /**
-     * Try to find class by matching explicit Import.
-     * @param name class name
-     * @return class object
-     */
-    private Class<?> resolveMatchingExplicitImport(String name) {
-        Class<?> clazz = null;
-        for (String imp : imports) {
-            // Very important to add the "." in the check below. Otherwise you
-            // when checking for "DataException", it will match on
-            // "SecurityDataException". This has been the cause of a very
-            // difficult bug to resolve!
-            if (imp.endsWith(PERIOD + name)) {
-                clazz = resolveQualifiedName(imp);
-                if (clazz != null) {
-                    break;
-                }
-            }
-        }
-        return clazz;
-    }
-
-    /**
-     * See if inner class of this class.
-     * @param name name of the search Class to search
-     * @param currentClass class where search in
-     * @return class if found , or null if not resolved
-     * @throws ClassNotFoundException  if an error occurs
-     */
-    private Class<?> resolveInnerClass(String name, String currentClass)
-            throws ClassNotFoundException {
-        Class<?> clazz = null;
-        if (!currentClass.isEmpty()) {
-            String innerClass = currentClass + DOLLAR_SIGN + name;
-
-            if (!pkg.isEmpty()) {
-                innerClass = pkg + PERIOD + innerClass;
-            }
-
-            if (isLoadable(innerClass)) {
-                clazz = safeLoad(innerClass);
-            }
-        }
-        return clazz;
-    }
-
-    /**
-     * Try star imports.
-     * @param name name of the Class to search
-     * @return  class if found , or null if not resolved
-     */
-    private Class<?> resolveByStarImports(String name) {
-        Class<?> clazz = null;
-        for (String imp : imports) {
-            if (imp.endsWith(".*")) {
-                final String fqn = imp.substring(0, imp.lastIndexOf('.') + 1) + name;
-                clazz = resolveQualifiedName(fqn);
-                if (clazz != null) {
-                    break;
-                }
-            }
-        }
-        return clazz;
-    }
-
-    /**
-     * Checks if the given class name can be loaded.
-     * @param name name of the class to check
-     * @return whether a specified class is loadable with safeLoad().
-     */
-    public boolean isLoadable(String name) {
-        boolean result;
-        try {
-            safeLoad(name);
-            result = true;
-        }
-        catch (final ClassNotFoundException | NoClassDefFoundError ignored) {
-            result = false;
-        }
-        return result;
-    }
-
-    /**
-     * Will load a specified class is such a way that it will NOT be
-     * initialised.
-     * @param name name of the class to load
-     * @return the {@code Class} for the specified class
-     * @throws ClassNotFoundException if an error occurs
-     * @throws NoClassDefFoundError if an error occurs
-     */
-    // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon.
-    private Class<?> safeLoad(String name) throws ClassNotFoundException, NoClassDefFoundError {
-        // The next line will load the class using the specified class
-        // loader. The magic is having the "false" parameter. This means the
-        // class will not be initialised. Very, very important.
-        return Class.forName(name, false, loader);
-    }
-
-    /**
-     * Tries to resolve a class for fully-specified name.
-     * @param name a given name of class.
-     * @return Class object for the given name or null.
-     */
-    private Class<?> resolveQualifiedName(final String name) {
-        Class<?> classObj = null;
-        try {
-            if (isLoadable(name)) {
-                classObj = safeLoad(name);
-            }
-            else {
-                //Perhaps it's fully-qualified inner class
-                final int dot = name.lastIndexOf('.');
-                if (dot != -1) {
-                    final String innerName =
-                        name.substring(0, dot) + DOLLAR_SIGN + name.substring(dot + 1);
-                    classObj = resolveQualifiedName(innerName);
-                }
-            }
-        }
-        catch (final ClassNotFoundException ex) {
-            // we shouldn't get this exception here,
-            // so this is unexpected runtime exception
-            throw new IllegalStateException(ex);
-        }
-        return classObj;
-    }
-
-}
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocMethodCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocMethodCheck.java
--- a/src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocMethodCheck.java
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocMethodCheck.java
@@ -30,6 +30,7 @@
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -49,10 +50,7 @@
 
 /**
  * <p>
- * Checks the Javadoc of a method or constructor. By default,
- * check does not validate methods and constructors for unused throws.
- * To allow documented exceptions derived from {@code java.lang.RuntimeException}
- * that are not declared, set property {@code allowUndeclaredRTE} to true.
+ * Checks the Javadoc of a method or constructor.
  * The scope to verify is specified using the {@code Scope} class and defaults
  * to {@code Scope.PRIVATE}. To verify another scope, set property scope to
  * a different <a href="https://checkstyle.org/property_types.html#scope">scope</a>.
@@ -60,10 +58,10 @@
  * <p>
  * Violates parameters and type parameters for which no param tags are present
  * can be suppressed by defining property {@code allowMissingParamTags}.
- * Violates exceptions which are declared to be thrown, but for which no throws
- * tag is present can be suppressed by defining property {@code allowMissingThrowsTags}.
  * Violates methods which return non-void but for which no return tag is present
  * can be suppressed by defining property {@code allowMissingReturnTag}.
+ * Violates exceptions which are declared to be thrown, but for which no throws
+ * tag is present by activation of property {@code validateThrows}.
  * </p>
  * <p>
  * Javadoc is not required on a method that is tagged with the {@code @Override}
@@ -87,10 +85,6 @@
  *                           JavadocTag[] aTags,
  *                           int aLineNo)
  * </pre>
- * <p>
- * The classpath may need to be configured to locate the class information.
- * The classpath configuration is dependent on the mechanism used to invoke Checkstyle.
- * </p>
  * <ul>
  * <li>
  * Property {@code allowedAnnotations} - Specify the list of annotations
@@ -111,52 +105,16 @@
  * Default value is {@code null}.
  * </li>
  * <li>
- * Property {@code allowUndeclaredRTE} - Control whether to allow documented exceptions
- * that are not declared if they are a subclass of {@code java.lang.RuntimeException}.
- * Default value is {@code false}.
- * </li>
- * <li>
- * Property {@code allowThrowsTagsForSubclasses} - Control whether to allow
- * documented exceptions that are subclass of one of declared exception.
- * Default value is {@code false}.
- * </li>
- * <li>
  * Property {@code allowMissingParamTags} - Control whether to ignore violations
  * when a method has parameters but does not have matching {@code param} tags in the javadoc.
  * Default value is {@code false}.
  * </li>
  * <li>
- * Property {@code allowMissingThrowsTags} - Control whether to ignore violations
- * when a method declares that it throws exceptions but does not have matching
- * {@code throws} tags in the javadoc.
- * Default value is {@code false}.
- * </li>
- * <li>
  * Property {@code allowMissingReturnTag} - Control whether to ignore violations
  * when a method returns non-void type and does not have a {@code return} tag in the javadoc.
  * Default value is {@code false}.
  * </li>
  * <li>
- * Property {@code logLoadErrors} - Control checkstyle's error handling when
- * a class loading fails. This check may need to load exception classes mentioned
- * in the {@code @throws} tag to check whether they are RuntimeExceptions.
- * If set to {@code false} a classpath configuration problem is assumed and
- * the TreeWalker stops operating on the class completely. If set to {@code true}
- * (the default), checkstyle assumes a typo or refactoring problem in the javadoc
- * and logs the problem in the normal checkstyle report (potentially masking
- * a configuration error).
- * Default value is {@code true}.
- * </li>
- * <li>
- * Property {@code suppressLoadErrors} - Control whether to suppress violations
- * when a class loading fails. When logLoadErrors is set to true, the TreeWalker
- * completely processes a class and displays any problems with loading exceptions
- * as checkstyle violations. When this property is set to true, the violations
- * generated when logLoadErrors is set true are suppressed from being reported as
- * violations in the checkstyle report.
- * Default value is {@code false}.
- * </li>
- * <li>
  * Property {@code tokens} - tokens to check Default value is:
  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
  * METHOD_DEF</a>,
@@ -173,36 +131,64 @@
  * &lt;module name="JavadocMethod"/&gt;
  * </pre>
  * <p>
- * To configure the check for {@code public} scope and to allow documentation
- * of undeclared RuntimeExceptions:
+ * To configure the check for {@code public} scope, ignoring any missing param tags is:
  * </p>
  * <pre>
  * &lt;module name="JavadocMethod"&gt;
  *   &lt;property name="scope" value="public"/&gt;
- *   &lt;property name="allowUndeclaredRTE" value="true"/&gt;
+ *   &lt;property name="allowMissingParamTags" value="true"/&gt;
  * &lt;/module&gt;
  * </pre>
  * <p>
- * To configure the check for for {@code public} scope, to allow documentation
- * of undeclared RuntimeExceptions, while ignoring any missing param tags is:
+ * To configure the check for methods which are in {@code private},
+ * but not in {@code protected} scope:
  * </p>
  * <pre>
  * &lt;module name="JavadocMethod"&gt;
- *   &lt;property name="scope" value="public"/&gt;
- *   &lt;property name="allowUndeclaredRTE" value="true"/&gt;
- *   &lt;property name="allowMissingParamTags" value="true"/&gt;
+ *   &lt;property name="scope" value="private"/&gt;
+ *   &lt;property name="excludeScope" value="protected"/&gt;
  * &lt;/module&gt;
  * </pre>
  * <p>
- * To configure the check for methods which are in {@code private},
- * but not in {@code protected} scope:
+ * To configure the check to validate {@code throws} tags, you can use following config.
+ * ATTENTION: Checkstyle does not have information about hierarchy of exception types so usage
+ * of base class is considered as separate exception type. As workaround you need to
+ * specify both types in javadoc (parent and exact type).
  * </p>
  * <pre>
  * &lt;module name="JavadocMethod"&gt;
- *   &lt;property name="scope" value="private"/&gt;
- *   &lt;property name="excludeScope" value="protected"/&gt;
+ *   &lt;property name="validateThrows" value="true"/&gt;
  * &lt;/module&gt;
  * </pre>
+ * <pre>
+ * &#47;**
+ *  * Actual exception thrown is child class of class that is declared in throws.
+ *  * It is limitation of checkstyle (as checkstyle does not know type hierarchy).
+ *  * Javadoc is valid not declaring FileNotFoundException
+ *  * BUT checkstyle can not distinguish relationship between exceptions.
+ *  * &#64;param file some file
+ *  * &#64;throws IOException if some problem
+ *  *&#47;
+ * public void doSomething8(File file) throws IOException {
+ *     if (file == null) {
+ *         throw new FileNotFoundException(); // violation
+ *     }
+ * }
+ *
+ * &#47;**
+ *  * Exact throw type referencing in javadoc even first is parent of second type.
+ *  * It is a limitation of checkstyle (as checkstyle does not know type hierarchy).
+ *  * This javadoc is valid for checkstyle and for javadoc tool.
+ *  * &#64;param file some file
+ *  * &#64;throws IOException if some problem
+ *  * &#64;throws FileNotFoundException if file is not found
+ *  *&#47;
+ * public void doSomething9(File file) throws IOException {
+ *     if (file == null) {
+ *         throw new FileNotFoundException();
+ *     }
+ * }
+ * </pre>
  *
  * @since 3.0
  */
@@ -279,57 +265,34 @@ public class JavadocMethodCheck extends AbstractCheck {
             CommonUtil.createPattern("\\{\\s*@(inheritDoc)\\s*\\}");
 
     /** Stack of maps for type params. */
-    private final Deque<Map<String, AbstractClassInfo>> currentTypeParams = new ArrayDeque<>();
+    private final Deque<Map<String, ClassInfo>> currentTypeParams = new ArrayDeque<>();
 
-    /** Imports details. **/
+    /**
+     * Imports details.
+     * @noinspection MismatchedQueryAndUpdateOfCollection
+     */
     private final Set<String> imports = new HashSet<>();
 
-    /** Full identifier for package of the method. **/
-    private FullIdent packageFullIdent;
-
     /** Name of current class. */
     private String currentClassName;
 
-    /** {@code ClassResolver} instance for current tree. */
-    private ClassResolver classResolver;
-
     /** Specify the visibility scope where Javadoc comments are checked. */
     private Scope scope = Scope.PRIVATE;
 
     /** Specify the visibility scope where Javadoc comments are not checked. */
     private Scope excludeScope;
 
-    /**
-     * Control whether to allow documented exceptions that are not declared if
-     * they are a subclass of {@code java.lang.RuntimeException}.
-     */
-    // -@cs[AbbreviationAsWordInName] We can not change it as,
-    // check's property is part of API (used in configurations).
-    private boolean allowUndeclaredRTE;
-
     /**
      * Control whether to validate {@code throws} tags.
      */
     private boolean validateThrows;
 
-    /**
-     * Control whether to allow documented exceptions that are subclass of one
-     * of declared exception.
-     */
-    private boolean allowThrowsTagsForSubclasses;
-
     /**
      * Control whether to ignore violations when a method has parameters but does
      * not have matching {@code param} tags in the javadoc.
      */
     private boolean allowMissingParamTags;
 
-    /**
-     * Control whether to ignore violations when a method declares that it throws
-     * exceptions but does not have matching {@code throws} tags in the javadoc.
-     */
-    private boolean allowMissingThrowsTags;
-
     /**
      * Control whether to ignore violations when a method returns non-void type
      * and does not have a {@code return} tag in the javadoc.
@@ -375,28 +338,6 @@ public void setExcludeScope(Scope excludeScope) {
         this.excludeScope = excludeScope;
     }
 
-    /**
-     * Setter to control whether to allow documented exceptions that are not declared if
-     * they are a subclass of {@code java.lang.RuntimeException}.
-     *
-     * @param flag a {@code Boolean} value
-     */
-    // -@cs[AbbreviationAsWordInName] We can not change it as,
-    // check's property is part of API (used in configurations).
-    public void setAllowUndeclaredRTE(boolean flag) {
-        allowUndeclaredRTE = flag;
-    }
-
-    /**
-     * Setter to control whether to allow documented exceptions that are subclass of one
-     * of declared exception.
-     *
-     * @param flag a {@code Boolean} value
-     */
-    public void setAllowThrowsTagsForSubclasses(boolean flag) {
-        allowThrowsTagsForSubclasses = flag;
-    }
-
     /**
      * Setter to control whether to ignore violations when a method has parameters
      * but does not have matching {@code param} tags in the javadoc.
@@ -407,16 +348,6 @@ public void setAllowMissingParamTags(boolean flag) {
         allowMissingParamTags = flag;
     }
 
-    /**
-     * Setter to control whether to ignore violations when a method declares that it throws
-     * exceptions but does not have matching {@code throws} tags in the javadoc.
-     *
-     * @param flag a {@code Boolean} value
-     */
-    public void setAllowMissingThrowsTags(boolean flag) {
-        allowMissingThrowsTags = flag;
-    }
-
     /**
      * Setter to control whether to ignore violations when a method returns non-void type
      * and does not have a {@code return} tag in the javadoc.
@@ -427,42 +358,9 @@ public void setAllowMissingReturnTag(boolean flag) {
         allowMissingReturnTag = flag;
     }
 
-    /**
-     * Setter to control checkstyle's error handling when a class loading fails.
-     * This check may need to load exception classes mentioned in the {@code @throws}
-     * tag to check whether they are RuntimeExceptions. If set to {@code false}
-     * a classpath configuration problem is assumed and the TreeWalker stops operating
-     * on the class completely. If set to {@code true}(the default), checkstyle assumes
-     * a typo or refactoring problem in the javadoc and logs the problem in the normal
-     * checkstyle report (potentially masking a configuration error).
-     *
-     * @param logLoadErrors true if errors should be logged
-     * @deprecated No substitute.
-     */
-    @Deprecated
-    public final void setLogLoadErrors(boolean logLoadErrors) {
-        // no code
-    }
-
-    /**
-     * Setter to control whether to suppress violations when a class loading fails.
-     * When logLoadErrors is set to true, the TreeWalker completely processes
-     * a class and displays any problems with loading exceptions as checkstyle violations.
-     * When this property is set to true, the violations generated when logLoadErrors
-     * is set true are suppressed from being reported as violations in the checkstyle report.
-     *
-     * @param suppressLoadErrors true if errors shouldn't be shown
-     * @deprecated No substitute.
-     */
-    @Deprecated
-    public final void setSuppressLoadErrors(boolean suppressLoadErrors) {
-        // no code
-    }
-
     @Override
     public final int[] getRequiredTokens() {
         return new int[] {
-            TokenTypes.PACKAGE_DEF,
             TokenTypes.IMPORT,
             TokenTypes.CLASS_DEF,
             TokenTypes.INTERFACE_DEF,
@@ -478,7 +376,6 @@ public int[] getDefaultTokens() {
     @Override
     public int[] getAcceptableTokens() {
         return new int[] {
-            TokenTypes.PACKAGE_DEF,
             TokenTypes.IMPORT,
             TokenTypes.CLASS_DEF,
             TokenTypes.ENUM_DEF,
@@ -491,21 +388,16 @@ public int[] getAcceptableTokens() {
 
     @Override
     public void beginTree(DetailAST rootAST) {
-        packageFullIdent = FullIdent.createFullIdent(null);
         imports.clear();
         // add java.lang.* since it's always imported
         imports.add("java.lang.*");
-        classResolver = null;
         currentClassName = "";
         currentTypeParams.clear();
     }
 
     @Override
     public final void visitToken(DetailAST ast) {
-        if (ast.getType() == TokenTypes.PACKAGE_DEF) {
-            processPackage(ast);
-        }
-        else if (ast.getType() == TokenTypes.IMPORT) {
+        if (ast.getType() == TokenTypes.IMPORT) {
             processImport(ast);
         }
         else if (ast.getType() == TokenTypes.CLASS_DEF
@@ -604,7 +496,9 @@ private void checkComment(DetailAST ast, TextBlock comment) {
                     && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations);
 
                 checkParamTags(tags, ast, reportExpectedTags);
-                checkThrowsTags(tags, getThrows(ast), reportExpectedTags);
+                final List<ExceptionInfo> throwed =
+                        combineExceptionInfo(getThrows(ast), getThrowed(ast));
+                checkThrowsTags(tags, throwed, reportExpectedTags);
                 if (CheckUtil.isNonVoidMethod(ast)) {
                     checkReturnTag(tags, ast.getLineNo(), reportExpectedTags);
                 }
@@ -810,6 +704,75 @@ private List<ExceptionInfo> getThrows(DetailAST ast) {
         return returnValue;
     }
 
+    /**
+     * Get ExceptionInfo for all exceptions that throws in method code by 'throw new'.
+     * @param methodAst method DetailAST object where to find exceptions
+     * @return list of ExceptionInfo
+     */
+    private List<ExceptionInfo> getThrowed(DetailAST methodAst) {
+        final List<ExceptionInfo> returnValue = new ArrayList<>();
+        final DetailAST blockAst = methodAst.findFirstToken(TokenTypes.SLIST);
+        if (blockAst != null) {
+            final List<DetailAST> throwLiterals = findTokensInAstByType(blockAst,
+                    TokenTypes.LITERAL_THROW);
+            for (DetailAST throwAst : throwLiterals) {
+                final DetailAST newAst = throwAst.getFirstChild().getFirstChild();
+                if (newAst.getType() == TokenTypes.LITERAL_NEW) {
+                    final FullIdent ident = FullIdent.createFullIdent(newAst.getFirstChild());
+                    final ExceptionInfo exceptionInfo = new ExceptionInfo(
+                            createClassInfo(new Token(ident), currentClassName));
+                    returnValue.add(exceptionInfo);
+                }
+            }
+        }
+        return returnValue;
+    }
+
+    /**
+     * Combine ExceptionInfo lists together by matching names.
+     * @param list1 list of ExceptionInfo
+     * @param list2 list of ExceptionInfo
+     * @return combined list of ExceptionInfo
+     */
+    private static List<ExceptionInfo> combineExceptionInfo(List<ExceptionInfo> list1,
+                                                     List<ExceptionInfo> list2) {
+        final List<ExceptionInfo> result = new ArrayList<>(list1);
+        for (ExceptionInfo expectionInfo : list2) {
+            if (result.stream().noneMatch(item -> isExceptionInfoSame(item, expectionInfo))) {
+                result.add(expectionInfo);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Finds node of specified type among root children, siblings, siblings children
+     * on any deep level.
+     * @param root    DetailAST
+     * @param astType value of TokenType
+     * @return {@link Optional} of {@link DetailAST} node which matches the predicate.
+     */
+    public static List<DetailAST> findTokensInAstByType(DetailAST root, int astType) {
+        final List<DetailAST> result = new ArrayList<>();
+        DetailAST curNode = root;
+        while (curNode != null) {
+            DetailAST toVisit = curNode.getFirstChild();
+            while (curNode != null && toVisit == null) {
+                toVisit = curNode.getNextSibling();
+                curNode = curNode.getParent();
+                if (curNode == root) {
+                    toVisit = null;
+                    break;
+                }
+            }
+            curNode = toVisit;
+            if (curNode != null && curNode.getType() == astType) {
+                result.add(curNode);
+            }
+        }
+        return result;
+    }
+
     /**
      * Checks a set of tags for matching parameters.
      *
@@ -970,31 +933,15 @@ private void checkThrowsTags(List<JavadocTag> tags,
             tagIt.remove();
 
             // Loop looking for matching throw
-            final String documentedEx = tag.getFirstArg();
             final Token token = new Token(tag.getFirstArg(), tag.getLineNo(), tag
                     .getColumnNo());
-            final AbstractClassInfo documentedClassInfo = createClassInfo(token,
+            final ClassInfo documentedClassInfo = createClassInfo(token,
                     currentClassName);
-            final boolean found = foundThrows.contains(documentedEx)
-                    || isInThrows(throwsList, documentedClassInfo, foundThrows);
-
-            // Handle extra JavadocTag.
-            if (!found) {
-                boolean reqd = true;
-                if (allowUndeclaredRTE) {
-                    reqd = !isUnchecked(documentedClassInfo.getClazz());
-                }
-
-                if (reqd && validateThrows) {
-                    log(tag.getLineNo(), tag.getColumnNo(),
-                        MSG_UNUSED_TAG,
-                        JavadocTagInfo.THROWS.getText(), tag.getFirstArg());
-                }
-            }
+            processThrows(throwsList, documentedClassInfo, foundThrows);
         }
         // Now dump out all throws without tags :- unless
         // the user has chosen to suppress these problems
-        if (!allowMissingThrowsTags && reportExpectedTags) {
+        if (validateThrows && reportExpectedTags) {
             throwsList.stream().filter(exceptionInfo -> !exceptionInfo.isFound())
                 .forEach(exceptionInfo -> {
                     final Token token = exceptionInfo.getName();
@@ -1011,128 +958,60 @@ private void checkThrowsTags(List<JavadocTag> tags,
      * @param throwsList list of throws
      * @param documentedClassInfo documented exception class info
      * @param foundThrows previously found throws
-     * @return true if documented exception is in throws.
      */
-    private boolean isInThrows(List<ExceptionInfo> throwsList,
-            AbstractClassInfo documentedClassInfo, Set<String> foundThrows) {
-        boolean found = false;
+    private static void processThrows(List<ExceptionInfo> throwsList,
+                                      ClassInfo documentedClassInfo, Set<String> foundThrows) {
         ExceptionInfo foundException = null;
 
         // First look for matches on the exception name
         for (ExceptionInfo exceptionInfo : throwsList) {
-            if (exceptionInfo.getName().getText().equals(
+            if (isClassNamesSame(exceptionInfo.getName().getText(),
                     documentedClassInfo.getName().getText())) {
-                found = true;
                 foundException = exceptionInfo;
                 break;
             }
         }
 
-        // Now match on the exception type
-        final ListIterator<ExceptionInfo> exceptionInfoIt = throwsList.listIterator();
-        while (!found && exceptionInfoIt.hasNext()) {
-            final ExceptionInfo exceptionInfo = exceptionInfoIt.next();
-
-            if (documentedClassInfo.getClazz() == exceptionInfo.getClazz()) {
-                found = true;
-                foundException = exceptionInfo;
-            }
-            else if (allowThrowsTagsForSubclasses) {
-                found = isSubclass(documentedClassInfo.getClazz(), exceptionInfo.getClazz());
-            }
-        }
-
         if (foundException != null) {
             foundException.setFound();
             foundThrows.add(documentedClassInfo.getName().getText());
         }
-
-        return found;
-    }
-
-    /**
-     * Is exception is unchecked (subclass of {@code RuntimeException}
-     * or {@code Error}.
-     *
-     * @param exception {@code Class} of exception to check
-     * @return true  if exception is unchecked
-     *         false if exception is checked
-     */
-    private static boolean isUnchecked(Class<?> exception) {
-        return isSubclass(exception, RuntimeException.class)
-            || isSubclass(exception, Error.class);
-    }
-
-    /**
-     * Checks if one class is subclass of another.
-     *
-     * @param child {@code Class} of class
-     *               which should be child
-     * @param parent {@code Class} of class
-     *                which should be parent
-     * @return true  if aChild is subclass of aParent
-     *         false otherwise
-     */
-    private static boolean isSubclass(Class<?> child, Class<?> parent) {
-        return parent != null && child != null
-            && parent.isAssignableFrom(child);
     }
 
     /**
-     * Returns the current tree's ClassResolver.
-     * @return {@code ClassResolver} for current tree.
+     * Check that ExceptionInfo objects are same by name.
+     * @param info1 ExceptionInfo object
+     * @param info2 ExceptionInfo object
+     * @return true is ExceptionInfo object have the same name
      */
-    private ClassResolver getClassResolver() {
-        if (classResolver == null) {
-            classResolver =
-                new ClassResolver(getClass().getClassLoader(),
-                                  packageFullIdent.getText(),
-                                  imports);
-        }
-        return classResolver;
+    private static boolean isExceptionInfoSame(ExceptionInfo info1, ExceptionInfo info2) {
+        return isClassNamesSame(info1.getName().getText(),
+                                    info2.getName().getText());
     }
 
     /**
-     * Attempts to resolve the Class for a specified name.
-     * @param resolvableClassName name of the class to resolve
-     * @param className name of surrounding class.
-     * @return the resolved class or {@code null}
-     *          if unable to resolve the class.
-     * @noinspection WeakerAccess
+     * Check that class names are same by short name of class. If some class name is fully
+     * qualified it is cut to short name.
+     * @param class1 class name
+     * @param class2 class name
+     * @return true is ExceptionInfo object have the same name
      */
-    // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon.
-    private Class<?> resolveClass(String resolvableClassName,
-                                          String className) {
-        Class<?> clazz;
-        try {
-            clazz = getClassResolver().resolve(resolvableClassName, className);
+    private static boolean isClassNamesSame(String class1, String class2) {
+        boolean result = false;
+        if (class1.equals(class2)) {
+            result = true;
         }
-        // -@cs[IllegalCatch] Exception type is not predictable.
-        catch (final Exception ignored) {
-            clazz = null;
+        else {
+            final String separator = ".";
+            if (class1.contains(separator) || class2.contains(separator)) {
+                final String class1ShortName = class1
+                        .substring(class1.lastIndexOf('.') + 1);
+                final String class2ShortName = class2
+                        .substring(class2.lastIndexOf('.') + 1);
+                result = class1ShortName.equals(class2ShortName);
+            }
         }
-        return clazz;
-    }
-
-    /**
-     * Tries to load class. Logs error if unable.
-     * @param ident name of class which we try to load.
-     * @param className name of surrounding class.
-     * @return {@code Class} for a ident.
-     * @noinspection WeakerAccess, MethodOnlyUsedFromInnerClass
-     */
-    // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon.
-    private Class<?> tryLoadClass(Token ident, String className) {
-        return resolveClass(ident.getText(), className);
-    }
-
-    /**
-     * Collects the details of a package.
-     * @param ast node containing the package details
-     */
-    private void processPackage(DetailAST ast) {
-        final DetailAST nameAST = ast.getLastChild().getPreviousSibling();
-        packageFullIdent = FullIdent.createFullIdent(nameAST);
+        return result;
     }
 
     /**
@@ -1152,7 +1031,7 @@ private void processTypeParams(DetailAST ast) {
         final DetailAST params =
             ast.findFirstToken(TokenTypes.TYPE_PARAMETERS);
 
-        final Map<String, AbstractClassInfo> paramsMap = new HashMap<>();
+        final Map<String, ClassInfo> paramsMap = new HashMap<>();
         currentTypeParams.push(paramsMap);
 
         if (params != null) {
@@ -1165,7 +1044,7 @@ private void processTypeParams(DetailAST ast) {
                     if (bounds != null) {
                         final FullIdent name =
                             FullIdent.createFullIdentBelow(bounds);
-                        final AbstractClassInfo classInfo =
+                        final ClassInfo classInfo =
                             createClassInfo(new Token(name), currentClassName);
                         final String alias =
                                 child.findFirstToken(TokenTypes.IDENT).getText();
@@ -1197,10 +1076,10 @@ private void processClass(DetailAST ast) {
      * @param surroundingClass name of surrounding class.
      * @return class info for given name.
      */
-    private AbstractClassInfo createClassInfo(final Token name,
-                                              final String surroundingClass) {
-        final AbstractClassInfo result;
-        final AbstractClassInfo classInfo = findClassAlias(name.getText());
+    private ClassInfo createClassInfo(final Token name,
+                                      final String surroundingClass) {
+        final ClassInfo result;
+        final ClassInfo classInfo = findClassAlias(name.getText());
         if (classInfo == null) {
             result = new RegularClass(name, surroundingClass, this);
         }
@@ -1216,12 +1095,12 @@ private AbstractClassInfo createClassInfo(final Token name,
      * @return ClassInfo for alias if it exists, null otherwise
      * @noinspection WeakerAccess
      */
-    private AbstractClassInfo findClassAlias(final String name) {
-        AbstractClassInfo classInfo = null;
-        final Iterator<Map<String, AbstractClassInfo>> iterator = currentTypeParams
+    private ClassInfo findClassAlias(final String name) {
+        ClassInfo classInfo = null;
+        final Iterator<Map<String, ClassInfo>> iterator = currentTypeParams
                 .descendingIterator();
         while (iterator.hasNext()) {
-            final Map<String, AbstractClassInfo> paramMap = iterator.next();
+            final Map<String, ClassInfo> paramMap = iterator.next();
             classInfo = paramMap.get(name);
             if (classInfo != null) {
                 break;
@@ -1233,7 +1112,7 @@ private AbstractClassInfo findClassAlias(final String name) {
     /**
      * Contains class's {@code Token}.
      */
-    private abstract static class AbstractClassInfo {
+    private static class ClassInfo {
 
         /** {@code FullIdent} associated with this class. */
         private final Token name;
@@ -1241,8 +1120,9 @@ private abstract static class AbstractClassInfo {
         /**
          * Creates new instance of class information object.
          * @param className token which represents class name.
+         * @throws IllegalArgumentException when className is nulls
          */
-        protected AbstractClassInfo(final Token className) {
+        protected ClassInfo(final Token className) {
             if (className == null) {
                 throw new IllegalArgumentException(
                     "ClassInfo's name should be non-null");
@@ -1250,13 +1130,6 @@ protected AbstractClassInfo(final Token className) {
             name = className;
         }
 
-        /**
-         * Returns class associated with that object.
-         * @return {@code Class} associated with an object.
-         */
-        // -@cs[ForbidWildcardAsReturnType] The class is deprecated and will be removed soon.
-        public abstract Class<?> getClazz();
-
         /**
          * Gets class name.
          * @return class name
@@ -1268,16 +1141,12 @@ public final Token getName() {
     }
 
     /** Represents regular classes/enums. */
-    private static final class RegularClass extends AbstractClassInfo {
+    private static final class RegularClass extends ClassInfo {
 
         /** Name of surrounding class. */
         private final String surroundingClass;
         /** The check we use to resolve classes. */
         private final JavadocMethodCheck check;
-        /** Is class loadable. */
-        private boolean loadable = true;
-        /** {@code Class} object of this class if it's loadable. */
-        private Class<?> classObj;
 
         /**
          * Creates new instance of of class information object.
@@ -1293,56 +1162,32 @@ private static final class RegularClass extends AbstractClassInfo {
             this.check = check;
         }
 
-        @Override
-        public Class<?> getClazz() {
-            if (loadable && classObj == null) {
-                setClazz(check.tryLoadClass(getName(), surroundingClass));
-            }
-            return classObj;
-        }
-
-        /**
-         * Associates {@code Class} with an object.
-         * @param clazz {@code Class} to associate with.
-         */
-        private void setClazz(Class<?> clazz) {
-            classObj = clazz;
-            loadable = clazz != null;
-        }
-
         @Override
         public String toString() {
             return "RegularClass[name=" + getName()
                     + ", in class='" + surroundingClass + '\''
                     + ", check=" + check.hashCode()
-                    + ", loadable=" + loadable
-                    + ", class=" + classObj
                     + ']';
         }
 
     }
 
     /** Represents type param which is "alias" for real type. */
-    private static class ClassAlias extends AbstractClassInfo {
+    private static class ClassAlias extends ClassInfo {
 
         /** Class information associated with the alias. */
-        private final AbstractClassInfo classInfo;
+        private final ClassInfo classInfo;
 
         /**
          * Creates new instance of the class.
          * @param name token which represents name of class alias.
          * @param classInfo class information associated with the alias.
          */
-        /* package */ ClassAlias(final Token name, AbstractClassInfo classInfo) {
+        /* package */ ClassAlias(final Token name, ClassInfo classInfo) {
             super(name);
             this.classInfo = classInfo;
         }
 
-        @Override
-        public final Class<?> getClazz() {
-            return classInfo.getClazz();
-        }
-
         @Override
         public String toString() {
             return "ClassAlias[alias " + getName() + " for " + classInfo.getName() + "]";
@@ -1420,7 +1265,7 @@ public String toString() {
     private static class ExceptionInfo {
 
         /** Class information associated with this exception. */
-        private final AbstractClassInfo classInfo;
+        private final ClassInfo classInfo;
         /** Does the exception have throws tag associated with. */
         private boolean found;
 
@@ -1429,7 +1274,7 @@ private static class ExceptionInfo {
          *
          * @param classInfo class info
          */
-        /* package */ ExceptionInfo(AbstractClassInfo classInfo) {
+        /* package */ ExceptionInfo(ClassInfo classInfo) {
             this.classInfo = classInfo;
         }
 
@@ -1454,14 +1299,6 @@ private Token getName() {
             return classInfo.getName();
         }
 
-        /**
-         * Gets exception class.
-         * @return class for this exception
-         */
-        private Class<?> getClazz() {
-            return classInfo.getClazz();
-        }
-
     }
 
 }


Test patch:
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/javadoc/ClassResolverTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/javadoc/ClassResolverTest.java
deleted file mode 100644
--- a/src/test/java/com/puppycrawl/tools/checkstyle/checks/javadoc/ClassResolverTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// checkstyle: Checks Java source code for adherence to a set of rules.
-// Copyright (C) 2001-2019 the original author or authors.
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-////////////////////////////////////////////////////////////////////////////////
-
-package com.puppycrawl.tools.checkstyle.checks.javadoc;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import org.junit.jupiter.api.Test;
-
-public class ClassResolverTest {
-
-    @Test
-    public void testResolveInPackage() throws ClassNotFoundException {
-        final Set<String> imports = new HashSet<>();
-        final ClassResolver classResolver = new ClassResolver(
-                Thread.currentThread().getContextClassLoader(), "java.util", imports);
-        assertNotNull(classResolver.resolve("List", ""), "Class should be resolved");
-        try {
-            classResolver.resolve("NoSuchClass", "");
-            fail("ClassNotFoundException is expected");
-        }
-        catch (ClassNotFoundException ex) {
-            // exception is expected
-            assertEquals("NoSuchClass", ex.getMessage(), "Invalid exception message");
-        }
-    }
-
-    @Test
-    public void testResolveMatchingExplicitImport() throws ClassNotFoundException {
-        final Set<String> imports = new HashSet<>();
-        imports.add("java.text.ChoiceFormat");
-        imports.add("no.such.package.ChoiceFormat");
-        final ClassResolver classResolver = new ClassResolver(
-                Thread.currentThread().getContextClassLoader(), null, imports);
-        assertNotNull(classResolver.resolve("ChoiceFormat", ""), "Class should be resolved");
-    }
-
-    @Test
-    public void testResolveByStarImports() throws ClassNotFoundException {
-        final Set<String> imports = new HashSet<>();
-        imports.add("no.such.package.*");
-        final ClassResolver classResolver = new ClassResolver(
-                Thread.currentThread().getContextClassLoader(), null, imports);
-        assertNotNull(classResolver.resolve("StringBuffer", ""), "Class should be resolved");
-    }
-
-    @Test
-    public void testExistedImportCantBeResolved() {
-        final Set<String> imports = new HashSet<>();
-        imports.add("java.applet.someClass");
-        final ClassResolver classResolver = new ClassResolver(
-                Thread.currentThread().getContextClassLoader(),
-                "", imports);
-
-        try {
-            classResolver.resolve("someClass", "");
-            fail("ClassNotFoundException is expected");
-        }
-        catch (ClassNotFoundException ex) {
-            // exception is expected
-            assertEquals("someClass", ex.getMessage(), "Invalid exception message");
-        }
-    }
-
-    @Test
-    public void testResolveInnerClass() throws Exception {
-        final Set<String> imports = new HashSet<>();
-        final ClassResolver classResolver = new ClassResolver(
-                Thread.currentThread().getContextClassLoader(),
-                "java.util", imports);
-
-        final Class<?> entry = classResolver.resolve("Entry", "Map");
-        assertEquals("java.util.Map$Entry", entry.getName(), "Invalid resolve result");
-    }
-
-    @Test
-    public void testResolveInnerClassWithEmptyPackage() {
-        final Set<String> imports = new HashSet<>();
-        final ClassResolver classResolver = new ClassResolver(
-                Thread.currentThread().getContextClassLoader(),
-                "", imports);
-
-        try {
-            classResolver.resolve("Entry", "Map");
-            fail("ClassNotFoundException is expected");
-        }
-        catch (ClassNotFoundException ex) {
-            // exception is expected
-            assertEquals("Entry", ex.getMessage(), "Invalid exception message");
-        }
-    }
-
-}
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocMethodCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocMethodCheckTest.java
--- a/src/test/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocMethodCheckTest.java
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocMethodCheckTest.java
@@ -27,8 +27,6 @@
 import static com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck.MSG_UNUSED_TAG_GENERAL;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.fail;
 
@@ -57,7 +55,6 @@ public void testGetAcceptableTokens() {
 
         final int[] actual = javadocMethodCheck.getAcceptableTokens();
         final int[] expected = {
-            TokenTypes.PACKAGE_DEF,
             TokenTypes.IMPORT,
             TokenTypes.CLASS_DEF,
             TokenTypes.ENUM_DEF,
@@ -70,14 +67,6 @@ public void testGetAcceptableTokens() {
         assertArrayEquals(expected, actual, "Default acceptable tokens are invalid");
     }
 
-    @Test
-    public void testLogLoadErrors() throws Exception {
-        final DefaultConfiguration config = createModuleConfig(JavadocMethodCheck.class);
-        config.addAttribute("logLoadErrors", "true");
-        config.addAttribute("allowUndeclaredRTE", "true");
-        verify(config, getPath("InputJavadocMethodLoadErrors.java"), CommonUtil.EMPTY_STRING_ARRAY);
-    }
-
     @Test
     public void extendAnnotationTest() throws Exception {
         final DefaultConfiguration config = createModuleConfig(JavadocMethodCheck.class);
@@ -94,6 +83,21 @@ public void allowedAnnotationsTest() throws Exception {
         verify(config, getPath("InputJavadocMethodAllowedAnnotations.java"), expected);
     }
 
+    @Test
+    public void testExtraThrows() throws Exception {
+        final DefaultConfiguration config = createModuleConfig(JavadocMethodCheck.class);
+        config.addAttribute("validateThrows", "true");
+        final String[] expected = {
+            "41:56: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "IllegalStateException"),
+            "56:23: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "IllegalArgumentException"),
+            "68:23: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "IllegalArgumentException"),
+            "80:23: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws",
+                    "java.lang.IllegalArgumentException"),
+            "119:23: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "FileNotFoundException"),
+        };
+        verify(config, getPath("InputJavadocMethodExtraThrows.java"), expected);
+    }
+
     @Test
     public void testTags() throws Exception {
         final DefaultConfiguration checkConfig = createModuleConfig(JavadocMethodCheck.class);
@@ -104,7 +108,6 @@ public void testTags() throws Exception {
             "33: " + getCheckMessage(MSG_RETURN_EXPECTED),
             "40:16: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "Exception"),
             "49:16: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "Exception"),
-            "53:9: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "WrongException"),
             "55:16: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "Exception"),
             "55:27: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "NullPointerException"),
             "60:22: " + getCheckMessage(MSG_EXPECTED_TAG, "@param", "aOne"),
@@ -115,10 +118,6 @@ public void testTags() throws Exception {
             "78:8: " + getCheckMessage(MSG_UNUSED_TAG, "@param", "Unneeded"),
             "79: " + getCheckMessage(MSG_UNUSED_TAG_GENERAL),
             "87:8: " + getCheckMessage(MSG_DUPLICATE_TAG, "@return"),
-            "178:8: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "ThreadDeath"),
-            "179:8: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "ArrayStoreException"),
-            "236:8: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "java.io.FileNotFoundException"),
-            "254:8: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "java.io.FileNotFoundException"),
             "256:28: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "IOException"),
             "262:8: " + getCheckMessage(MSG_UNUSED_TAG, "@param", "aParam"),
             "305: " + getCheckMessage(MSG_RETURN_EXPECTED),
@@ -133,7 +132,6 @@ public void testTags() throws Exception {
     @Test
     public void testTagsWithResolver() throws Exception {
         final DefaultConfiguration checkConfig = createModuleConfig(JavadocMethodCheck.class);
-        checkConfig.addAttribute("allowUndeclaredRTE", "true");
         checkConfig.addAttribute("validateThrows", "true");
         final String[] expected = {
             "18:9: " + getCheckMessage(MSG_UNUSED_TAG, "@param", "unused"),
@@ -151,8 +149,6 @@ public void testTagsWithResolver() throws Exception {
             "78:8: " + getCheckMessage(MSG_UNUSED_TAG, "@param", "Unneeded"),
             "79: " + getCheckMessage(MSG_UNUSED_TAG_GENERAL),
             "87:8: " + getCheckMessage(MSG_DUPLICATE_TAG, "@return"),
-            "236:8: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "java.io.FileNotFoundException"),
-            "254:8: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "java.io.FileNotFoundException"),
             "256:28: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "IOException"),
             "262:8: " + getCheckMessage(MSG_UNUSED_TAG, "@param", "aParam"),
             "305: " + getCheckMessage(MSG_RETURN_EXPECTED),
@@ -216,7 +212,6 @@ public void testScopeAnonInnerAnonInner() throws Exception {
     @Test
     public void testScopeAnonInnerWithResolver() throws Exception {
         final DefaultConfiguration checkConfig = createModuleConfig(JavadocMethodCheck.class);
-        checkConfig.addAttribute("allowUndeclaredRTE", "true");
         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
         verify(checkConfig, getPath("InputJavadocMethodScopeAnonInner.java"), expected);
     }
@@ -224,7 +219,6 @@ public void testScopeAnonInnerWithResolver() throws Exception {
     @Test
     public void testTagsWithSubclassesAllowed() throws Exception {
         final DefaultConfiguration checkConfig = createModuleConfig(JavadocMethodCheck.class);
-        checkConfig.addAttribute("allowThrowsTagsForSubclasses", "true");
         checkConfig.addAttribute("validateThrows", "true");
         final String[] expected = {
             "18:9: " + getCheckMessage(MSG_UNUSED_TAG, "@param", "unused"),
@@ -242,8 +236,6 @@ public void testTagsWithSubclassesAllowed() throws Exception {
             "78:8: " + getCheckMessage(MSG_UNUSED_TAG, "@param", "Unneeded"),
             "79: " + getCheckMessage(MSG_UNUSED_TAG_GENERAL),
             "87:8: " + getCheckMessage(MSG_DUPLICATE_TAG, "@return"),
-            "178:8: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "ThreadDeath"),
-            "179:8: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "ArrayStoreException"),
             "256:28: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "IOException"),
             "262:8: " + getCheckMessage(MSG_UNUSED_TAG, "@param", "aParam"),
             "305: " + getCheckMessage(MSG_RETURN_EXPECTED),
@@ -294,7 +286,6 @@ public void testExcludeScope() throws Exception {
     public void testAllowMissingJavadocTags() throws Exception {
         final DefaultConfiguration checkConfig = createModuleConfig(JavadocMethodCheck.class);
         checkConfig.addAttribute("allowMissingParamTags", "true");
-        checkConfig.addAttribute("allowMissingThrowsTags", "true");
         checkConfig.addAttribute("allowMissingReturnTag", "true");
         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
         verify(checkConfig, getPath("InputJavadocMethodMissingJavadocTags.java"), expected);
@@ -306,7 +297,6 @@ public void testDoAllowMissingJavadocTagsByDefault() throws Exception {
         final String[] expected = {
             "10: " + getCheckMessage(MSG_RETURN_EXPECTED),
             "20:26: " + getCheckMessage(MSG_EXPECTED_TAG, "@param", "number"),
-            "30:42: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "ThreadDeath"),
             "51: " + getCheckMessage(MSG_RETURN_EXPECTED),
             "61: " + getCheckMessage(MSG_RETURN_EXPECTED),
             "72: " + getCheckMessage(MSG_RETURN_EXPECTED),
@@ -374,8 +364,6 @@ public void test11684082() throws Exception {
     @Test
     public void test11684083() throws Exception {
         final DefaultConfiguration checkConfig = createModuleConfig(JavadocMethodCheck.class);
-        checkConfig.addAttribute("allowThrowsTagsForSubclasses", "true");
-        checkConfig.addAttribute("allowUndeclaredRTE", "true");
         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
         verify(checkConfig, getPath("InputJavadocMethod_03.java"), expected);
     }
@@ -383,13 +371,10 @@ public void test11684083() throws Exception {
     @Test
     public void testGenerics1() throws Exception {
         final DefaultConfiguration checkConfig = createModuleConfig(JavadocMethodCheck.class);
-        checkConfig.addAttribute("allowThrowsTagsForSubclasses", "true");
-        checkConfig.addAttribute("allowUndeclaredRTE", "true");
         checkConfig.addAttribute("validateThrows", "true");
         final String[] expected = {
             "17:34: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "RE"),
             "33:13: " + getCheckMessage(MSG_EXPECTED_TAG, "@param", "<NPE>"),
-            "40:12: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "E"),
             "43:38: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "RuntimeException"),
             "44:13: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "java.lang.RuntimeException"),
         };
@@ -399,12 +384,10 @@ public void testGenerics1() throws Exception {
     @Test
     public void testGenerics2() throws Exception {
         final DefaultConfiguration checkConfig = createModuleConfig(JavadocMethodCheck.class);
-        checkConfig.addAttribute("allowThrowsTagsForSubclasses", "true");
         checkConfig.addAttribute("validateThrows", "true");
         final String[] expected = {
             "17:34: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "RE"),
             "33:13: " + getCheckMessage(MSG_EXPECTED_TAG, "@param", "<NPE>"),
-            "40:12: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "E"),
             "43:38: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "RuntimeException"),
             "44:13: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "java.lang.RuntimeException"),
         };
@@ -416,10 +399,8 @@ public void testGenerics3() throws Exception {
         final DefaultConfiguration checkConfig = createModuleConfig(JavadocMethodCheck.class);
         checkConfig.addAttribute("validateThrows", "true");
         final String[] expected = {
-            "8:8: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "RE"),
             "17:34: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "RE"),
             "33:13: " + getCheckMessage(MSG_EXPECTED_TAG, "@param", "<NPE>"),
-            "40:12: " + getCheckMessage(MSG_UNUSED_TAG, "@throws", "E"),
             "43:38: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "RuntimeException"),
             "44:13: " + getCheckMessage(MSG_EXPECTED_TAG, "@throws", "java.lang.RuntimeException"),
         };
@@ -429,8 +410,6 @@ public void testGenerics3() throws Exception {
     @Test
     public void test1379666() throws Exception {
         final DefaultConfiguration checkConfig = createModuleConfig(JavadocMethodCheck.class);
-        checkConfig.addAttribute("allowThrowsTagsForSubclasses", "true");
-        checkConfig.addAttribute("allowUndeclaredRTE", "true");
         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
         verify(checkConfig, getPath("InputJavadocMethod_1379666.java"), expected);
     }
@@ -495,20 +474,9 @@ public void testConstructor() throws Exception {
         verify(checkConfig, getPath("InputJavadocMethodConstructor.java"), expected);
     }
 
-    @Test
-    public void testIsSubclassWithNulls() throws Exception {
-        final JavadocMethodCheck check = new JavadocMethodCheck();
-        final Method method = check.getClass()
-                .getDeclaredMethod("isSubclass", Class.class, Class.class);
-        method.setAccessible(true);
-        assertFalse((boolean) method.invoke(check, null, null),
-                "Should return false if at least one of the params is null");
-    }
-
     @Test
     public void testGetRequiredTokens() {
         final int[] expected = {
-            TokenTypes.PACKAGE_DEF,
             TokenTypes.IMPORT,
             TokenTypes.CLASS_DEF,
             TokenTypes.INTERFACE_DEF,
@@ -566,18 +534,9 @@ public void testClassRegularClass() throws Exception {
         toString.setAccessible(true);
         final String result = (String) toString.invoke(regularClass);
         final String expected = "RegularClass[name=Token[blablabla(1x1)], in class='sur', check="
-                + methodCheck.hashCode() + "," + " loadable=true, class=null]";
+                + methodCheck.hashCode() + "]";
 
         assertEquals(expected, result, "Invalid toString result");
-
-        final Method setClazz = regularClass.getClass().getDeclaredMethod("setClazz", Class.class);
-        setClazz.setAccessible(true);
-        final Class<?> arg = null;
-        setClazz.invoke(regularClass, arg);
-
-        final Method getClazz = regularClass.getClass().getDeclaredMethod("getClazz");
-        getClazz.setAccessible(true);
-        assertNull(getClazz.invoke(regularClass), "Expected null");
     }
 
     @Test
@@ -603,7 +562,7 @@ public void testClassAliasToString() throws Exception {
                 "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck$ClassAlias");
         final Class<?> abstractTypeInfoType = Class
                 .forName("com.puppycrawl.tools.checkstyle.checks.javadoc."
-                        + "JavadocMethodCheck$AbstractClassInfo");
+                        + "JavadocMethodCheck$ClassInfo");
 
         final Constructor<?> classAliasConstructor = classAliasType
                 .getDeclaredConstructor(tokenType, abstractTypeInfoType);
@@ -620,19 +579,8 @@ public void testClassAliasToString() throws Exception {
     @Test
     public void testWithoutLogErrors() throws Exception {
         final DefaultConfiguration config = createModuleConfig(JavadocMethodCheck.class);
-        config.addAttribute("allowUndeclaredRTE", "true");
         verify(config, getPath("InputJavadocMethodLoadErrors.java"),
                 CommonUtil.EMPTY_STRING_ARRAY);
     }
 
-    @Test
-    public void testWithSuppressLoadErrors() throws Exception {
-        final DefaultConfiguration checkConfig = createModuleConfig(JavadocMethodCheck.class);
-        checkConfig.addAttribute("suppressLoadErrors", "true");
-        checkConfig.addAttribute("allowUndeclaredRTE", "true");
-        final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
-
-        verify(checkConfig, getPath("InputJavadocMethodLoadErrors.java"), expected);
-    }
-
 }
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/internal/powermock/ClassResolverPowerTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/internal/powermock/ClassResolverPowerTest.java
deleted file mode 100644
--- a/src/test/java/com/puppycrawl/tools/checkstyle/internal/powermock/ClassResolverPowerTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-////////////////////////////////////////////////////////////////////////////////
-// checkstyle: Checks Java source code for adherence to a set of rules.
-// Copyright (C) 2001-2019 the original author or authors.
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-////////////////////////////////////////////////////////////////////////////////
-
-package com.puppycrawl.tools.checkstyle.internal.powermock;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-
-import java.util.HashSet;
-import java.util.Set;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
-import com.puppycrawl.tools.checkstyle.checks.javadoc.ClassResolver;
-import com.puppycrawl.tools.checkstyle.checks.javadoc.ClassResolverTest;
-
-@RunWith(PowerMockRunner.class)
-@PrepareForTest({ ClassResolver.class, ClassResolverTest.class })
-public class ClassResolverPowerTest {
-
-    @Test
-    public void testResolveQualifiedNameFails() throws Exception {
-        final Set<String> imports = new HashSet<>();
-        imports.add("java.applet.someClass");
-
-        final ClassResolver classResolver = PowerMockito.spy(new ClassResolver(Thread
-                .currentThread().getContextClassLoader(), "", imports));
-
-        PowerMockito.doThrow(new ClassNotFoundException("expected exception"))
-                .when(classResolver, "safeLoad", any());
-        PowerMockito.doReturn(true).when(classResolver, "isLoadable", any());
-
-        try {
-            classResolver.resolve("someClass", "");
-            fail("IllegalStateException is expected");
-        }
-        catch (IllegalStateException ex) {
-            // exception is expected
-            final String expected = "expected exception";
-            assertTrue("Invalid exception cause, should be: ClassNotFoundException",
-                    ex.getCause() instanceof ClassNotFoundException);
-            assertTrue("Invalid exception message, should end with: " + expected,
-                    ex.getMessage().endsWith(expected));
-        }
-    }
-
-    /**
-     * This test exists to prevent any possible regression and let of
-     * https://github.com/checkstyle/checkstyle/issues/1192 to be persistent
-     * event is not very obvious.
-     *
-     * @throws Exception when smth is not expected
-     */
-    @Test
-    public void testIsLoadableWithNoClassDefFoundError() throws Exception {
-        final Set<String> imports = new HashSet<>();
-        imports.add("java.applet.someClass");
-
-        final ClassResolver classResolver = PowerMockito.spy(new ClassResolver(Thread
-                .currentThread().getContextClassLoader(), "", imports));
-
-        PowerMockito.doThrow(new NoClassDefFoundError("expected exception"))
-                .when(classResolver, "safeLoad", any());
-
-        final boolean result = classResolver.isLoadable("someClass");
-        assertFalse("result should be false", result);
-    }
-
-}
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/javadoc/javadocmethod/InputJavadocMethodExtraThrows.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/javadoc/javadocmethod/InputJavadocMethodExtraThrows.java
new file mode 100644
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/javadoc/javadocmethod/InputJavadocMethodExtraThrows.java
@@ -0,0 +1,136 @@
+package com.puppycrawl.tools.checkstyle.checks.javadoc.javadocmethod;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class InputJavadocMethodExtraThrows {
+
+    /**
+     * Extra throws in javadoc it is ok.
+     * @param properties some value
+     * @throws IllegalArgumentException when argument is wrong // ok
+     * @throws NullPointerException indicates null was passed // ok
+     */
+    public InputJavadocMethodExtraThrows(String properties) {
+        // here is NPE possible
+        if (properties.charAt(0) == 0) {
+            throw new IllegalArgumentException("cannot have char with code 0");
+        }
+    }
+
+    /**
+     * Extra throws in javadoc it is ok
+     * @param properties some value
+     * @throws java.lang.IllegalArgumentException when argument is wrong // ok
+     * @throws java.lang.NullPointerException indicates null was passed // ok
+     */
+    public void doSomething(String properties) throws IllegalArgumentException {
+        // here is NPE possible
+        if (properties.charAt(0) == 0) {
+            throw new IllegalArgumentException("cannot have char with code 0");
+        }
+    }
+
+    /**
+     * declared exception in method signature is missed in javadoc // violation
+     * @param properties some value
+     * @throws java.lang.IllegalArgumentException when argument is wrong // ok
+     * @throws java.lang.NullPointerException indicates null was passed // ok
+     */
+    public void doSomething2(String properties) throws IllegalStateException {
+        // here is NPE possible
+        if (properties.charAt(0) == 0) {
+            throw new IllegalArgumentException("cannot have char with code 0");
+        }
+    }
+
+    /**
+     * exception is explitly thrown in code missed in javadoc // violation
+     * @param properties some value
+     * @throws java.lang.IllegalStateException when argument is wrong // ok
+     */
+    public void doSomething3(String properties) throws IllegalStateException {
+        // here is NPE possible
+        if (properties.charAt(0) == 0) {
+            throw new IllegalArgumentException("cannot have char with code 0");
+        }
+    }
+
+    /**
+     * exception is explitly thrown in code missed in javadoc // violation
+     * @param properties some value
+     * @throws java.lang.IllegalStateException when argument is wrong // ok
+     */
+    public void doSomething4(String properties) {
+        // here is NPE possible
+        if (properties.charAt(0) == 0) {
+            throw new IllegalArgumentException("cannot have char with code 0");
+        }
+    }
+
+    /**
+     * exception is explitly thrown in code missed in javadoc // violation
+     * @param properties some value
+     * @throws java.lang.IllegalStateException when argument is wrong // ok
+     */
+    public void doSomething5(String properties) {
+        // here is NPE possible
+        if (properties.charAt(0) == 0) {
+            throw new java.lang.IllegalArgumentException("cannot have char with code 0");
+        }
+    }
+
+    /**
+     * expicitly throwed is declared in javadoc // ok
+     * @param properties some value
+     * @throws IllegalArgumentException when argument is wrong // ok
+     */
+    public void doSomething6(String properties) {
+        // here is NPE possible
+        if (properties.charAt(0) == 0) {
+            throw new java.lang.IllegalArgumentException("cannot have char with code 0");
+        }
+    }
+
+    /**
+     * expicitly throwed  but throwed as variable, we do not catch this for now // ok
+     * @param properties some value
+     */
+    public void doSomething7(String properties) {
+        // here is NPE possible
+        if (properties.charAt(0) == 0) {
+            IllegalArgumentException exception =
+                    new IllegalArgumentException("cannot have char with code 0");
+            throw exception;
+        }
+    }
+
+    /**
+     * Actual exception thrown is child class of class that is declared in throws.
+     * It is limitation of checkstyle (as checkstyle does not know type hierarchy).
+     * Javadoc is valid not declaring {@link FileNotFoundException}
+     * BUT checkstyle can not distinguish relationship between exceptions.
+     * @param file some file
+     * @throws IOException if some problem
+     */
+    public void doSomething8(File file) throws IOException {
+        if (file == null) {
+            throw new FileNotFoundException();
+        }
+    }
+
+   /**
+    * Exact throw type referencing in javadoc even first is parent of second type.
+    * It is a limitation of checkstyle (as checkstyle does not know type hierarchy).
+    * This javadoc is valid for checkstyle and for javadoc tool.
+    * @param file some file
+    * @throws IOException if some problem
+    * @throws FileNotFoundException if file is not found
+    */
+    public void doSomething9(File file) throws IOException {
+        if (file == null) {
+            throw new FileNotFoundException();
+        }
+    }
+}