Recursividade chatinha

E aê, pessoal.
To com uma parada tensa aqui.

Tenho um método onde eu passo uma categoria, e vou montando minha query conforme o número de subcategorias que estão relacionadas a ela e conforme as subcategorias da subcategoria, etc… posteriormente esse método me retorna uma lista de produtos.
Mas não estou conseguindo montar a recusividade, por conta da quantidade de detalhes que essa “montagem” da query possui.

Numa das tentativas ainda tentei montar for encadeado limitando o nível de subcategorias à 4, o que me ajudaria e muito, mas acusou ArrayBoundOutException no índice 3, não me lembro o nome ao certo.

Segue minha entidade Categoria:

@Entity
public class Categoria implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "id", nullable = false)
    private Integer id;
    @Column(name = "descricao", length = 100)
    private String descricao;
    @Column(name = "ativo")
    private Boolean ativo;
    @Column(name = "precoeditavel")
    private Boolean precoEditavel;
    @Column(name = "taxaitem")
    private Boolean taxaItem;
    @OneToMany(mappedBy = "categoria", fetch = FetchType.LAZY)
    private List<Produto> produtoList;
    @OneToMany(mappedBy = "categoria", fetch = FetchType.LAZY)
    private List<Categoria> categoriaList;
    @JoinColumn(name = "idcategoria", referencedColumnName = "id")
    @ManyToOne(fetch = FetchType.LAZY)
    private Categoria categoria;
}

Segue o código do meu controller:

ProdutoController

    public List<Produto> getRelListaPrecos(Categoria cat, Produto prodInicial, Produto prodFinal) {

        int cont = 0;
        String qry = "";
        Object[][] matriz = new Object[3][2];

        qry = "SELECT p FROM Produto p "
                + "LEFT OUTER JOIN FETCH p.produtoTrib "
                + "LEFT OUTER JOIN FETCH p.produtoEstoque "
                + "LEFT OUTER JOIN FETCH p.produtoValor "
                + "LEFT OUTER JOIN FETCH p.categoria "
                + "LEFT OUTER JOIN FETCH p.embalagem ";

        if (cat.getId() != 0) {
            //Uma categoria
            qry += cont > 0 ? "AND " : "WHERE ";
            qry += "p.categoria = :cat ";

            matriz[cont][0] = "cat";
            matriz[cont][1] = cat;
            cont++;
            if (!cat.getCategoriaList().isEmpty()) {

/*
*
*
* apartir daqui começaria a recursividade
*
*
*/
           for(Categoria subcat : cat.getCategoriaList()){
            
               qry += cont > 0 ? "OR " : "WHERE ";
               qry += "p.categoria = :subcat ";

               matriz[cont][0] = "subcat";
               matriz[cont][1] = subcat;
               cont++;
            }
   
            }
        }

        if (prodInicial != null && prodFinal != null) {
            qry += cont > 0 ? "AND " : "WHERE ";

            switch (Constantes.TIPO_CODIGO) {
                case 0:
                    qry += "p.id BETWEEN :codIni AND :codFim ";

                    matriz[cont][0] = "codIni";
                    matriz[cont][1] = prodInicial.getId();
                    cont++;

                    matriz[cont][0] = "codFim";
                    matriz[cont][1] = prodFinal.getId();
                    cont++;
                    break;
                case 1:

              //...continua o case

            }
        }

        switch (Constantes.TIPO_CODIGO) {
            case 0:
                qry += "ORDER BY p.id";
                break;
            case 1:
                qry += "ORDER BY p.id";
                break;
            case 2:
                qry += "ORDER BY p.codigo";
                break;
        }

        return service.findByFiltro(qry, matriz);
    }

Bem, como dá pra ver eu preciso das minhas 2 variáveis iniciadas no começo do método e da matriz, até o final dele.
meu service recebe a query montada e um array de Objects.

Aberto a perguntas.
Se alguém me ajudar a concluir meu pensamento, eu ficaria muito grato. rs
[]s