Tests with The One Billion Row Challenge (1BRC)

Have you ever ventured into the realm of reading massive files with native Java code? How about tackling a challenge that involves dealing with a staggering 1,000,000,000 rows? That’s precisely what Gunnar Morling’s One Billion Row Challenge invites you to explore. The challenge remains open until January 31, 2024, see the official repository for more information.

Your mission, should you choose to accept it, is to craft a Java program capable of extracting temperature measurement values from a text file and computing the minimum, mean, and maximum temperatures per weather station. The catch? The file is a behemoth, boasting a whopping 1,000,000,000 rows!

The challenge repository has already garnered over 100 submissions, showcasing a myriad of ingenious approaches to achieve optimal performance. Strategies include delving into Unsafe mode, manipulating bytes, employing Threads, Virtual Threads, and leveraging the Arena API to manage memory segment lifecycles efficiently. The creative solutions span a spectrum of techniques, such as flexible allocation and timely deallocation.

Over the past two days, I’ve dedicated my efforts to implementing a solution that meets the baseline values of around 4 minutes (spoiler: the faster that got was 2 min). Along this journey, I’ve uncovered some valuable insights. For instance, when dealing with large files using native Java features (without Unsafe, etc.), the most effective method involves using Files.lines(Paths.get(FILE)). In my tests, employing the Stream API in parallel yielded even better results.

[java]
private static void run() throws IOException {
    ConcurrentHashMap<String, Result> data = new ConcurrentHashMap<>();

    // Processing time: 122,740 ms
    try (var lines = Files.lines(Paths.get(FILE)).parallel()) {
        lines.forEach(line -> {
            String[] split = line.split(";");
            data.computeIfAbsent(split[0], r -> new Result()).calculate(split[1]);
        });
    }

    System.out.println(new TreeMap<>(data));
}

Attempts to use Files.readAllLines(null) or collect lines into a list for parallel processing proved challenging due to the inherent limitations when handling large files. The Stream API emerges as the recommended approach for efficiently processing substantial datasets.

I also experimented with Virtual Threads, but surprisingly, I observed no performance improvement. In fact, in my tests, Virtual Threads in conjunction with parallel processing fared worse than the Stream API parallel approach.

[java]
private static void run3() throws IOException {
    ConcurrentHashMap<String, Result> data = new ConcurrentHashMap<>();

    // Processing time: 194,468 ms
    try (var lines = Files.lines(Paths.get(FILE)).parallel()) {
        lines.forEach(line -> {
            Thread.startVirtualThread(() -> {
                String[] split = line.split(";");
                data.computeIfAbsent(split[0], r -> new Result()).calculate(split[1]);
            });
        });
    }

    System.out.println(new TreeMap<>(data));
}

Interestingly, some of the swifter implementations employ Unsafe mode, involving byte chunking, reading, and performing calculations, as seen in Quan Anh Mai’s implementation. When Attempts to adapt this code for Virtual Threads did not yield a significant improvement. From

[java]
var thread = new Thread(() -> resultList[index] = processFile(data, offset, limit));

//TO

threadList[index] = Thread.ofVirtual().start(() -> {
resultList[index] = processFile(data, offset, limit);
});

Another change that I’ve made was changing from:

[java] 
target.compute(key, (k, v) -> {
if (v == null) {
v = new Aggregator();
}

v.min = Math.min(v.min, UNSAFE.getShort(this.data, baseOffset + MIN_OFFSET));
v.max = Math.max(v.max, UNSAFE.getShort(this.data, baseOffset + MAX_OFFSET));
v.sum += UNSAFE.getLong(this.data, baseOffset + SUM_OFFSET);
v.count += UNSAFE.getLong(this.data, baseOffset + COUNT_OFFSET);
return v;
});

to

[java]
target.computeIfAbsent(key, k -> new Aggregator()).calculate(this.data, baseOffset);

But, again, I haven’t notice any significant improvement using computeIfAbsent instead of the compute(…) and the if (v == null) {

Of course, the Aggregator was implementing all the min, max etc calculation with my change:

public void calculate(Object data, long baseOffset) {
            min = Math.min(min, UNSAFE.getShort(data, baseOffset + 4));
            max = Math.max(max, UNSAFE.getShort(data, baseOffset + 6));
            sum += UNSAFE.getLong(data, baseOffset + 8);
            count += UNSAFE.getLong(data, baseOffset + 16);
        }

Anyway, the journey has been enlightening, and you can explore my implementation at this GitHub repository. While I’m unsure if I’ll submit a pull request given the abundance of impressive code, the experience has been enriching, and I’ve gleaned valuable insights along the way.

Monitoring Keycloak using Dashbuilder

Keycloak Dashbuilder

This is an example that connects to Keycloak REST API as a proxy and builds Dashboards using the Dashbuilder [1] project. You can see an example below:

So, you can now build your own dashboards using Dashbuilder YAML and analyze your data easily. Also, you can find the project on the GitHub repository [3]

Pre-Requisites

  • Java 17
  • Maven installed
  • Download Keycloak 20.0.x or higher


Install and Setup

Configuring Keycloak

1. First, you need to install and run Keycloak [2]

2. Import the realm-export.json [4] on your running Keycloak environment

3. Create a new User at Keycloak Dashbuilder Realm. On this new user go to the Role Mapping tab, find by realm-admin role, and assign it to the new user created. This new user should use this role in order to have admin permissions.

4. Make sure that Events are enabled on Realm Settings -> Events tab. Check the User events settings and Admin events settings configurations.

User Events

Admin Events

5. Go to Clients and click on dashboards client, then go to Credentials and copy the Client Secret value and save.

Configure and run Keycloak-dashbuilder project

1. Clone or download the keycloak-dashbuilder on [3]

2. on src/resources/application.properties edit the following paramenters:

quarkus.http.port=8081
quarkus.http.cors=true
api.keycloak.base-url=[A]

quarkus.oidc.auth-server-url=[B]
quarkus.oidc.client-id=dashboards
quarkus.oidc.application-type=web-app
quarkus.http.auth.permission.authenticated.paths=/*
quarkus.http.auth.permission.authenticated.policy=authenticated

quarkus.http.auth.permission.public.paths=/q/dev
quarkus.http.auth.permission.public.policy=permit

quarkus.oidc.credentials.secret=[C]

3. You can now compile and run the project

./mvnw quarkus:dev

4. Finally you can access the Dashboards URL at http://localhost:8081/dashboards/. You should be redirected to the Keycloak Login Page, so, log in with the created user.

Dashboards

Extras

You can also, compile and install [keycloak-metrics-spi [5] and keycloak-health-checks [6] as your SPI and use it.

References

[1] https://www.dashbuilder.org/
[2] https://www.keycloak.org/downloads
[3] https://github.com/pedro-hos/keycloak-dashbuilder
[4] https://github.com/pedro-hos/keycloak-dashbuilder/blob/main/config/realm-export.json
[5] https://github.com/aerogear/keycloak-metrics-spi
[6] https://github.com/thomasdarimont/keycloak-health-checks

Adicionando RStudio e outros Custom Notebooks no Open Data Hub

Este é mais um post sobre Open Data Hub ou Red Hat Openshift Data Science, se você quer saber mais sobre o produto, pode acessar o meu post anterior: Instalando Open Data Hub 1.4.1 no Openshift, além do site AI on OpenShift e do site oficial do Open Data Hub.

Custom notebooks são úteis para os casos em que se necessário utilizar uma versão de biblioteca diferente do que é fornecido pela imagem padrão do Open Data Hub, ou então utilizar alguma ferramenta ou editor que não esteja presente na imagem default do Open Data Hub. Outra opção seria criar sua própria imagem. É possível consultar algumas opções de imagens já criadas no repositório: github.com/opendatahub-io-contrib/workbench-images, como por exemplo RStudio, Jupyter, VSCode etc. Vamos utilizar a imagem do RStudio deste repositório.

Read More

Instalando Open Data Hub 1.4.1 no Openshift

Open Data Hub (ODH) é um projeto opensource que disponibiliza diversas ferramentas para a entrega end-to-end de uma aplicação de Inteligência Artificial ou Machine Learn, auxiliando assim a adoção da cultura de MLOps, você por saber mais sobre MLOps no artigo MLOps: pipelines de entrega contínua e automação no aprendizado de máquina e também em Enterprise MLOps Reference Design. O Open Data Hub é desenvolvido para o Openshift (Kubernetes) e por isso carrega consigo todos os benefícios da ferramente, seja na hora de criar, manter ou realizar deploy de aplicações.

ODH também fornece diversas ferramentas opensource para workflows de AI e Machine Learning (ML) como: Jupyter Notebook, ODH Dashboard, Data Science Papelines e Model Mesh Serving. Você pode consultar a documentação para saber mais sobre cada um desses itens. Em suma, O Open Data Hub (ou o produto da Red Hat “OpenShift Data Science“) tem por objetivo cobrir todas etapas do lifecycle de Machine Learn:

  1. Coletar e preparar os dados com intuito de garantir que os dados seam completos, e de uma boa qualidade para serem usados na próxima etapa, no treinamento dos modelos;
  2. Desenvolver o modelo, isso é: Treinar, Testar e escolher o melhor modelo para o fim prposto, com melhor acurácia na predição;
  3. Integração do modelo na aplicação. Parte importente no processo de entrega de uma ML é a integração com o produto ou aplicação final, gerando assim valor para o produto final;
  4. Por fim, Monitoritorar e gerenciar o modelo criado para garantir a acurácia do modelo, caso a acuária não esteja adequada, um novo modelo é gerado, a partir de novos dados, então todo processo é iniciado novamente.
Read More

How to encrypt the Quarkus File Vault secret?

If you need to encrypt the Java Keystore secret using the Quakus File Vault extension and haven’t the secret as plain text on application.properties file, follow the next steps. If you need to know how to use the Quarkus File Vault see the previous post How to protect your database password on Quakus using Java Keystore? or the Oficial Documentation.

Pre requisites

  • Java 11+
  • Maven installed

Installation

First, you need to fork and clone and build the Encryption Tool. So, go to https://github.com/quarkiverse/quarkus-file-vault/tree/main/vault-utils fork and clone the project. The next step is to run the following command:

mvn clean install

How to use the tool.

You can explore the options by using the --help command:

java -jar target/quarkus-app/quarkus-run.jar --help
Usage: Encrypt Secret Util [-hV] [-e=<encryptionKey>] -p=<keystorePassword>
  -e, --encryption-key=<encryptionKey>
                  (optional) Encryption Key
  -h, --help      Show this help message and exit.
  -p, --keystore-password=<keystorePassword>
                  (mandatory) Keystore password
  -V, --version   Print version information and exit.

The mandatory parameters are the Keystore password: -p, --keystore-password. The encryption-key will be generated automatically and encrypted as well. The encryption ley must be at least 16 characters long.

How to encrypt the secret?

You can encrypt the Keystore password like this:

java -jar target/quarkus-app/quarkus-run.jar -p storepassword -e fyM6UDOgqeBe2BU7VjmVAtKuuFuOIrh1

You should see something like that at the output:

######################################################################################################
Please, add the following paramenters on your application.properties file, and replace the <name> value!
The <name> will be used in the consumer to refer to this provider.

quarkus.file.vault.provider.<name>.encryption-key=fyM6UDOgqeBe2BU7VjmVAtKuuFuOIrh1
quarkus.file.vault.provider.<name>.secret=DInwWiCJZKBBiwLU9IsdWS6m966OR6rlv3TtOj0jlTpP3GdXP6dPjmCP
######################################################################################################

So, your application.properties file should be something like that

quarkus.datasource.db-kind=postgresql 
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_orm_test
quarkus.datasource.jdbc.max-size=16
quarkus.datasource.credentials-provider=quarkus.file.vault.provider.db1
 
quarkus.file.vault.provider.db1.path=dbpasswords.p12
quarkus.file.vault.provider.db1.alias=root
quarkus.file.vault.provider.db1.encryption-key=fyM6UDOgqeBe2BU7VjmVAtKuuFuOIrh1
quarkus.file.vault.provider.db1.secret=DInwWiCJZKBBiwLU9IsdWS6m966OR6rlv3TtOj0jlTpP3GdXP6dPjmCP

Now, the secret is encrypted.

Protecting the encryption-key and secret with ConfigSource.

You can also keep the encryption-key and secret on Java source code using ConfigSource as following:

package io.quarkiverse.filevault.it;

import java.util.Set;

import org.eclipse.microprofile.config.spi.ConfigSource;

public class KeyStoreConfigSource implements ConfigSource {

    @Override
    public Set<String> getPropertyNames() {
        return Set.of("db1.storepassword");
    }

    @Override
    public String getValue(String propertyName) {
        return "db1.storepassword".equals(propertyName) ? "DInwWiCJZKBBiwLU9IsdWS6m966OR6rlv3TtOj0jlTpP3GdXP6dPjmCP" : null;
    }

    @Override
    public String getName() {
        return "file-vault-config-source";
    }

}

and also, the following class to read the encryption-key

package io.quarkiverse.filevault.it;

import java.util.Set;

import org.eclipse.microprofile.config.spi.ConfigSource;

public class EncryptionKeyConfigSource implements ConfigSource {

    @Override
    public Set<String> getPropertyNames() {
        return Set.of("db1.encryption-key");
    }

    @Override
    public String getValue(String propertyName) {
        return "db1.encryption-key".equals(propertyName) ? "fyM6UDOgqeBe2BU7VjmVAtKuuFuOIrh1" : null;
    }

    @Override
    public String getName() {
        return "encryption-key-config-source";
    }

}

Then your application.properties file should be like:

quarkus.datasource.db-kind=postgresql 
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_orm_test
quarkus.datasource.jdbc.max-size=16
quarkus.datasource.credentials-provider=quarkus.file.vault.provider.db1
 
quarkus.file.vault.provider.db1.path=dbpasswords.p12
quarkus.file.vault.provider.db1.alias=root
quarkus.file.vault.provider.db1.encryption-key=${db1.encryption-key}
quarkus.file.vault.provider.db1.secret=${db1.storepassword}

How to protect your database password on Quakus using Java Keystore?

Let’s assume here that you have the following database configuration in your application.properties file on your Quarkus.io project.

quarkus.datasource.db-kind=postgresql 
quarkus.datasource.username=root
quarkus.datasource.password=secret
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_orm_test
quarkus.datasource.jdbc.max-size=16

So, you don’t want to have the property quarkus.datasource.password=secret in plain text. The alternative is to store this password in a Java Keystore and read the password inside that Keystore to connect to the database. We’ll use the Quarkus File Vault project to have this security layer.

The Quarkus File Vault project provides Quarkus CredentialsProvider which extracts passwords and other sensitive data from Java KeyStore. Java KeyStore is used as a file-based Vault. Sensitive data can be imported to and securely stored in this Vault as Java SecretKey values. Imported certificates are also supported.

Installation

The first step is to add the Quakus File Vault extension to your project

<dependency>
    <groupId>io.quarkiverse.file-vault</groupId>
    <artifactId>quarkus-file-vault</artifactId>
    <version>0.5.0</version>
</dependency>

Get Started

  • First we need to create the Java Keystore and store the password on it. So, run the following command.
    • Use the -alias with the database username;
    • -keystore is the keystore file;
    • -storepass is the secret that should be used to open the keystore and retrieve the password
keytool -importpass -alias root -keystore dbpasswords.p12 -storepass storepassword -storetype PKCS12

How to use the extension.

Now, we can update the application.properties to use the Keystore instead the password in plain text.

quarkus.datasource.db-kind=postgresql 
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/hibernate_orm_test
quarkus.datasource.jdbc.max-size=16
quarkus.datasource.credentials-provider=quarkus.file.vault.provider.db1

quarkus.file.vault.provider.db1.path=dbpasswords.p12
quarkus.file.vault.provider.db1.secret=storepassword
quarkus.file.vault.provider.db1.alias=root
  • The quarkus.datasource.credentials-provider refers to the Vault configurations;
  • The quarkus.file.vault.provider.db1.path is the Java Keystore file;
  • the quarkus.file.vault.provider.db1.secret is the Java Keystore secret used to “open” the Keystore and retrieve the passwords present on it;
  • Finally, the quarkus.file.vault.provider.db1.alias is the alias created during the Java Keystore creation process, and also our dababase password.
    • The Quarkus File Vault uses the alias as user as default. You can set quarkus.file.vault.set-alias-as-user=false and set the database username quarkus.datasource.username=root

Now, you are able to store the dabase password in a Java Keystore. You can also encrypt the quarkus.file.vault.provider.db1.secret as described on: How to encrypt the Quarkus File Vault secret?

Unsupervised Machine Learning: Análise de Cluster em R

A primeira etapa desse tutorial, é de fato, enteder a diferença entre Algoritmos de Aprendizado de Máquina Não Supervisionados, ou Unsupervised Machine Learning e Supervisionados ou Supervised Machine Learning , vamos então para algumas definições rápidas:

  • Unsupervised Machine Learning: Nos algoritmos de machine learning não supervisionados, ou seja, sem supervisão, são aqueles que não existem um parametro para dizer se uma predição está correta ou não, cabe ao próprio algoritmo identificar as semelhanças e agrupa-las de acordo com um padrão. Exemplo: Dado uma coleção de dados de notas de alunos, queros agrupar esses alunos de acordo com essas notas, para então identificar quais alunos têm dificildades em certas matérias.
  • Supervised Machine Learning: Nos algoritmos de machine learning supervisionados, esses rótulos já são conhecidos, e queremos saber se uma determinada nova entrada ou conjunto, é ou não pertencente ao que a máquina foi treinada. Segundo Joel Grus em Data Science do Zero – Primeiras Regras com o Python “[…] aprendizado supervisionado […] começam com um conjunto de dados rotulados, e os usam como base para fazer previsões sobre novos dados, não rotulados […]”. Problemas de aprendizagem supervisionados podem ser de “regressão” (número real) e “classificação” (finito e não ordenado). Em um problema de regressão, estamos tentando prever os resultados em uma saída contínua, o que significa que estamos a tentando mapear variáveis ​​de entrada para alguma função contínua. Em um problema de classificação, estamos tentando prever os resultados em uma saída discreta. Em outras palavras, estamos tentando mapear variáveis ​​de entrada em categorias distintas. Exemplo: I. Classificação: Dado um email, identificar se o mesmo é SPAM ou não, ou ainda identificar se um vídeo contém imagens para adultos ou não. II. Regressão: Validar o valor de um carro a partir do seu ano de fabricação e fabricante. Abaixo uma imagem que reflete bem os dois modelos.

No exemplo de hoje, vamos abordar um modelo de agrupamento, ou clustering que está dentro do universo de aprendizado de máquina não supervisionado. Queremos a apartir de um conjunto de dados, agrupar seus semelhantes, ou seja, dentro dos grupos a variabilidade deve ser mínima, e a variabilidade entre grupos devem ser máxima.

Read More

07 – Keycloak com Quarkus.io

Pré-requisitos

Configuração Keycloak

  • Iniciar o Keycloak com port-offset de 100:
$ cd bin

$ ./standalone.sh -Djboss.socket.binding.port-offset=100
  • Criar novo Realm quarkus
  • Criar uma Role ‘user’ e uma ‘admin’
  • Criar um usuário novo ‘user1’ com a Role ‘user’
  • Criar um novo Client clicando no menu Client e depois Create:
    • Client ID: quarkus-app
    • Client Protocol: openid-connect
    • Access Type: confidential
    • Valid Redirect URIs: http://localhost:8080/
    • Save

Configuração Quarkus.io

  • Criar o projeto quarkus com Keycloak e Resteasy-jsonb de extensions
mvn io.quarkus:quarkus-maven-plugin:0.20.0:create \

    -DprojectGroupId=br.com.pedrohos \

    -DprojectArtifactId=keycloak-quarkus \

    -Dextensions="keycloak, resteasy-jsonb"
  • Importar na sua IDE preferida
  • Criar classe usuário com o conteúdo a seguir:
asdasdsa
package br.com.pedrohos;
import org.keycloak.KeycloakSecurityContext;
public class Usuario {
    
    private final String nome;

    public Usuario(KeycloakSecurityContext context) {
        this.nome = context.getToken().getPreferredUsername();
    }
    
    public String getNome() {
        return nome;
   }
}
  • Criar classe MyResource.java com o conteudo a seguir
package br.com.pedrohos;

import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.annotations.cache.NoCache;
import org.keycloak.KeycloakSecurityContext;

@Path("/api")
public class MyResource {

    @Inject
    KeycloakSecurityContext keycloakSecurityContext;

    @GET
    @Path("/me")
    @RolesAllowed("user")
    @Produces(MediaType.APPLICATION_JSON)
    @NoCache
    public Usuario minhasInfos() {
        return new Usuario(keycloakSecurityContext);
    }

    @GET
    @Path("/admin")
    @RolesAllowed("admin")
    @Produces(MediaType.TEXT_PLAIN)
    public String admin() {
        return "Admin infos";
    }
}
  • salvar a instalação do client do keycloak em src/main/resources

exemplo:

{
  "realm": "quarkus",
  "auth-server-url": "http://localhost:8180/auth",
  "ssl-required": "external",
  "resource": "quarkus-app",
  "credentials": {
    "secret": "secret"
  },

  "confidential-port": 0
}
  • Executar a aplicação:
./mvnw clean compile quarkus:dev

Testando aplicação

 

  • Recuperar Token
export access_token=$(\
    curl -X POST http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/token \
    --user quarkus-app:secret \
    -H 'content-type: application/x-www-form-urlencoded' \
    -d 'username=user1&password=1234&grant_type=password' | jq --raw-output '.access_token' \

 )
  • Acessar endpoints
curl -v -X GET \
  http://localhost:8080/api/me \
  -H "Authorization: Bearer "$access_token
curl -v -X GET \
  http://localhost:8080/api/admin \
  -H "Authorization: Bearer "$access_token

 

06 – Keycloak com Spring boot

Pré-requisitos

Configuração Keycloak

  • Iniciar o Keycloak com port-offset de 100:
$ cd bin

$ ./standalone.sh -Djboss.socket.binding.port-offset=100
  • Criar uma Role ‘user’
  • Criar um usuário novo com a Role ‘user’
  • Criar um novo Client clicando no menu Client e depois Create:

Configuração Spring Boot

  • Acessar: https://start.spring.io e criar um novo projeto com:
    • Maven Project
    • Java Language
    • Spring Boot 2.1.7
    • group: br.com.pedrohos
    • Artifact: keycloak-springboot
    • Dependencies: Spring Web Starter e Thymeleaf
  • Importar na sua IDE preferida
  • Criar uma classe WebController.java com o conteudo:
package br.com.pedrohos.keycloakspringboot.controllers;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class WebController {

    @GetMapping(path = "/")
    public String index() {
        return "index";
    }

    @GetMapping(path = "/private")
    public String secure(Model model) {
        model.addAttribute("message", "This is a private page!");
        return "secure";
    }
}

 

  • Criar o resources/templates/index.html:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Public Page</title>
    </head>
    <body>
        <h1>This is a public page</h1>
        <p>Get your greeting <a href="/private">here</a></p>
     </body>
</html>
  • Criar o resources/templates/secure.html:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head> 
    <title>Secure Page</title> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
    <p th:text="${message}" />
</body>
</html>

 

  • Editar o resources/application.properties
keycloak.auth-server-url=http://localhost:8180/auth

keycloak.realm=master

keycloak.resource=springboot-app

keycloak.public-client=true

keycloak.security-constraints[0].authRoles[0]=user

keycloak.security-constraints[0].securityCollections[0].patterns[0]=/private/*
  • Editar o pom.xml
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.keycloak.bom</groupId>
				<artifactId>keycloak-adapter-bom</artifactId>
				<version>6.0.1</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependency>
	     <groupId>org.keycloak</groupId>
	     <artifactId>keycloak-spring-boot-starter</artifactId>
	</dependency>
  • Iniciar o projeto:
mvn clean spring-boot:run