pentaho/pentaho-kettle · error · SessionRecoveryRetryException

Unable to invoke repository operation after reconnect

Error message

Unable to invoke repository operation after reconnect

What it means

RepositorySessionTimeoutHandler.reconnectAndRetry throws SessionRecoveryRetryException('Unable to invoke repository operation after reconnect') when the reflective re-invocation fails with IllegalAccessException or IllegalArgumentException — i.e. reflection could not legally call the method (inaccessible method, mismatched argument types/receiver), rather than the method itself failing. This indicates a programming or proxy mismatch problem, not a server problem.

Solutions

  1. Ensure the intercepted repository methods are public and the argument types exactly match the Method object's declaring class
  2. Re-obtain the Method object from the current instance's class instead of caching it across reconnects
  3. Check for plugin classloader conflicts after reconnection

Example fix

// before
Method m = Repository.class.getMethod( "save", ... ); // cached, may mismatch instance
// after
Method m = repository.getClass().getMethod( "save", ... ); // resolve from live instance
Defensive patterns

Strategy: validation

Validate before calling

// verify the method is reflectively invokable before reconnect flows
boolean isInvokable( Object receiver, Method m, Object[] args ) {
  if ( !m.getDeclaringClass().isInstance( receiver ) ) return false;
  Class<?>[] pt = m.getParameterTypes();
  if ( args.length != pt.length ) return false;
  for ( int i = 0; i < pt.length; i++ ) {
    if ( args[i] != null && !pt[i].isInstance( args[i] ) ) return false;
  }
  return Modifier.isPublic( m.getModifiers() );
}

Try / catch

try {
  handler.handle( proxy, method, args );
} catch ( SessionRecoveryRetryException e ) {
  if ( e.getCause() instanceof IllegalAccessException
       || e.getCause() instanceof IllegalArgumentException ) {
    log.error( "Reflection mismatch in timeout handler", e.getCause() ); // bug, not transient
  } else { throw e; }
}

Prevention

When it happens

Trigger: The repository proxy passes arguments that no longer match the method signature, the method is not accessible from the handler's package/class loader, or the receiver instance is of the wrong type after reconnection.

Common situations: Classloader issues with plugins after reconnect; custom Repository implementations whose methods are not public; version upgrade changed a method signature the handler caches.

Related errors


AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/87a729565c70a6ee. Report an issue: GitHub.

Appendix: source

Thrown at plugins/repositories/core/src/main/java/org/pentaho/di/ui/repo/timeout/RepositorySessionTimeoutHandler.java:151

  private Object reconnectAndRetry( Method method, Object[] args )
      throws SessionRecoveryRetryException {
    try {
      ConnectionManager.getInstance().reset();
    } catch ( Exception ce ) {
      // Intentionally ignore cache reset errors - they should not prevent reconnection
    }
    try {
      metaStoreInstance = wrapMetastoreWithTimeoutHandler( MetaStoreConst.getDefaultMetastore(), sessionTimeoutHandler );
    } catch ( Exception me ) {
      // Intentionally ignore metastore refresh errors - they should not prevent reconnection
    }
    try {
      return method.invoke( this.repository, args );
    } catch ( InvocationTargetException retryEx ) {
      throw new SessionRecoveryRetryException( "Failed to retry repository operation after reconnect",
          retryEx.getCause() );
    } catch ( IllegalAccessException | IllegalArgumentException retryEx ) {
      throw new SessionRecoveryRetryException( "Unable to invoke repository operation after reconnect", retryEx );
    }
  }

  static class SessionRecoveryRetryException extends KettleException {
    private static final long serialVersionUID = 1L;

    SessionRecoveryRetryException( String message, Throwable cause ) {
      super( message, cause );
    }
  }

  boolean connectedToRepository() {
    return repository.isConnected();
  }

  IRepositoryService wrapRepositoryServiceWithTimeoutHandler( Class<? extends IRepositoryService> clazz )
    throws KettleException {
    IRepositoryService service = repository.getService( clazz );

View on GitHub (pinned to f3058517a1)