A melhor forma de fazer isso é com JavaScript mesmo.
Você tem jQuery nesse seu projeto? Ele vai ajudar bastante.
Vamos fazer com um exemplo básico: Estado e Cidades.
Primeiramente, devemos carregar todos os estados:
<select id="estado" name="estado">
<c:forEach items="${estadoList}" var="e">
<option value="${e.uf}">${e.name}</option>
</c:forEach>
</select>
<select id="cidade" name="cidade">
</select>
<!--// coloca a url onde vamos buscar as cidades em uma variável aqui -->
<script>
var urlCidades = '<c:url value="/ajax/cidades/" />';
</script>
Depois, precisamos dizer ao jQuery que queremos buscar as cidades quando a combobox de estado sofrer alterações:
Coloque o JavaScript sempre em um arquivo separado.
$('#estado').on('change', function(){
var ufSelecionada = $(this).val();
$.ajax({
url: urlCidades,
data: {uf:ufSelecionada},
type : 'get',
dataType: 'json',
success : function(cidades) {
$('#cidade').empty(); // Precisa limpar a combo antes.
// no java vc faz cidades.get(0).getName() e cidades.get(0).getId(),
// Com JSON vc vai fazer cidades[0].name e cidades[0].id
for (var i = 0; i < cidades.length; i++){
$('#cidade').append('<option value="' + cidades[i].id + '">' + cidades[i].name + '</option>');
}
}
});
});
Depois, precisamos criar a action que vai carregar as cidades baseado no estado que foi selecionado:
@Get("/ajax/cidades/") // Mesma URL lá de cima.
public void carregaCidades (String uf){ // Variável está com o mesmo nome que a passada em data: {uf:ufSelecionada}
List<Cidade> cidades = new ArrayList<Cidade>();
// Carrega cidades baseada na uf selecionada...
result.use(Results.json()).withoutRoot().from(cidades).serialize();
}
O JSON, nesse caso, tem esse formato:
[
{id: 1, name: ‘São Paulo’}
]
Espero ter ajudado, qualquer dúvida, é só perguntar.