Me parece que a lógica da tua recursão não está muito clara. Basicamente quando for trabalhar com recursão em arrays deve ter em mente que precisa “reduzir” o array até ele chegar ao tamanho zero, e tudo indica que você não está reduzindo teu array, logo, o motivo do StackOverFlow.
O mais comum quando aplicando recursão, em primeira instância você precisa passar um “head” (que pode ser o primeiro elemento do teu array), e o “tail” (restante do array).
A solução do @Dragoon parece ser mais enxuta, mas pra deixar um pouco mais claro e didático, uma função que retorna o tail do array poderia ser algo do tipo:
public static int[] tailOf(int[] arr) {
if (arr.length <= 1) {
return new int[] {};
}
return Arrays.copyOfRange(arr, 1, arr.length);
}
Dentro do primeiro método, precisa chamar o segundo passando o head (primeiro elemento) e o restante:
return minor(arr[0], tailOf(arr));
O segundo método só precisa receber um inteiro (que representa o menor valor) e um array (tail), e neste caso, pra evitar o overflow, a primeira instrução tem que ser algo do tipo:
public static int minor(int min, int[] tail) {
// isso significa que quando não houver mais elementos no array,
// já logo retorna o menor valor encontrado
if (tail.length == 0) {
return min;
}
...
Pra finalizar, a lógica pra retornar o menor consiste em comparar o primeiro elemento do tail que foi passado com o menor encontrado. Caso o primeiro elemento seja menor que o menor encontrado, chama a função recursivamente passando o primeiro elemento (que agora é o menor) e o tail do array. Caso contrário, chama a função recursivamente passando o atual menor encontrado e o restante do array.
if (tail[0] < min) {
return minor(tail[0], tailOf(tail));
} else {
return minor(min, tailOf(tail));
}
Esse é um approach não tão comum em linguagens imperativas, mais entre linguagens funcionais, onde é comum ter suporte a tail call optimization. Mas acho que pra fins didáticos, ajuda bem a entender a lógica da recursão.