A guide to migrating the Auth0 Java SDK from v2 to v3.
Both v2 and v3 require Java 8 or above.
This major version change does not affect the Authentication API. The AuthAPI class has been ported directly from v2 to v3. Any code written for the Authentication API in the v2 version should work in the v3 version.
// Works in both v2 and v3
AuthAPI auth = AuthAPI.newBuilder("{YOUR_DOMAIN}", "{YOUR_CLIENT_ID}", "{YOUR_CLIENT_SECRET}").build();V3 introduces significant improvements to the Management API SDK by migrating to Fern as the code generation tool. This provides:
- Better resource grouping with sub-client organization
- Type-safe request and response objects using builder patterns
- Automatic pagination with
SyncPagingIterable<T> - Simplified access to HTTP response metadata via
withRawResponse() - Consistent method naming (
list,create,get,update,delete)
The Management API client initialization has changed from ManagementAPI to ManagementApi, and uses a different builder pattern.
v2:
import com.auth0.client.mgmt.ManagementAPI;
// Using domain and token
ManagementAPI mgmt = ManagementAPI.newBuilder("{YOUR_DOMAIN}", "{YOUR_API_TOKEN}").build();
// Using TokenProvider
TokenProvider tokenProvider = SimpleTokenProvider.create("{YOUR_API_TOKEN}");
ManagementAPI mgmt = ManagementAPI.newBuilder("{YOUR_DOMAIN}", tokenProvider).build();v3: 1st Approach : Standard Token-Based
import com.auth0.client.mgmt.ManagementApi;
ManagementApi client = ManagementApi
.builder()
.url("https://{YOUR_DOMAIN}/api/v2")
.token("{YOUR_API_TOKEN}")
.build();or
2nd Approach : OAuth client credentials flow
OAuthTokenSupplier tokenSupplier = new OAuthTokenSupplier(
"{CLIENT_ID}",
"{CLIENT_SECRET}",
"https://{YOUR_DOMAIN}",
"{YOUR_AUDIENCE}"
);
ClientOptions clientOptions = ClientOptions.builder()
.environment(Environment.custom("https://{YOUR_AUDIENCE}"))
.addHeader("Authorization", () -> "Bearer " + tokenSupplier.get())
.build();
ManagementApi client = new ManagementApi(clientOptions);| Option | v2 | v3 |
|---|---|---|
| Domain/URL | newBuilder(domain, token) |
.url("https://domain/api/v2") |
| Token | Constructor parameter | .token(token) |
| Timeout | Via HttpOptions |
.timeout(seconds) |
| Max retries | Via HttpOptions |
.maxRetries(count) |
| Custom HTTP client | .withHttpClient(Auth0HttpClient) |
.httpClient(OkHttpClient) |
| Custom headers | Not directly supported | .addHeader(name, value) |
V3 introduces a hierarchical sub-client structure. Operations on related resources are now accessed through nested clients instead of methods on a flat entity class.
v2:
// All user operations on UsersEntity
Request<User> userRequest = mgmt.users().get("user_id", new UserFilter());
Request<List<Permission>> permissionsRequest = mgmt.users().getPermissions("user_id", new PermissionsFilter());
Request<List<Role>> rolesRequest = mgmt.users().getRoles("user_id", new RolesFilter());
Request<LogEventsPage> logsRequest = mgmt.users().getLogEvents("user_id", new LogEventFilter());v3:
// Operations organized into sub-clients
GetUserResponseContent user = client.users().get("user_id");
SyncPagingIterable<Permission> permissions = client.users().permissions().list("user_id");
SyncPagingIterable<Role> roles = client.users().roles().list("user_id");
SyncPagingIterable<LogEvent> logs = client.users().logs().list("user_id");| v2 Method | v3 Sub-client |
|---|---|
mgmt.users().getPermissions() |
client.users().permissions().list() |
mgmt.users().getRoles() |
client.users().roles().list() |
mgmt.users().getLogEvents() |
client.users().logs().list() |
mgmt.users().getOrganizations() |
client.users().organizations().list() |
mgmt.users().link() |
client.users().identities().link() |
mgmt.users().unlink() |
client.users().identities().delete() |
mgmt.users().deleteMultifactorProvider() |
client.users().multifactor().deleteProvider() |
mgmt.organizations().getMembers() |
client.organizations().members().list() |
mgmt.organizations().getInvitations() |
client.organizations().invitations().list() |
mgmt.organizations().getEnabledConnections() |
client.organizations().enabledConnections().list() |
mgmt.actions().getVersions() |
client.actions().versions().list() |
mgmt.actions().getTriggerBindings() |
client.actions().triggers().bindings().list() |
mgmt.guardian().getFactors() |
client.guardian().factors().list() |
mgmt.branding().getUniversalLoginTemplate() |
client.branding().templates().getUniversalLogin() |
mgmt.connections().getScimConfiguration() |
client.connections().scimConfiguration().get() |
V3 uses type-safe request content objects with builders instead of domain objects or filter parameters.
v2:
import com.auth0.json.mgmt.users.User;
import com.auth0.net.Request;
// Creating a user
User user = new User("Username-Password-Authentication");
user.setEmail("test@example.com");
user.setPassword("password123".toCharArray());
Request<User> request = mgmt.users().create(user);
User createdUser = request.execute().getBody();v3:
import com.auth0.client.mgmt.types.CreateUserRequestContent;
import com.auth0.client.mgmt.types.CreateUserResponseContent;
// Creating a user
CreateUserResponseContent user = client.users().create(
CreateUserRequestContent
.builder()
.connection("Username-Password-Authentication")
.email("test@example.com")
.password("password123")
.build()
);| Aspect | v2 | v3 |
|---|---|---|
| Request building | Domain objects with setters | Builder pattern with *RequestContent types |
| Response type | Request<T> requiring .execute().getBody() |
Direct return of response object |
| Filtering | Filter classes (e.g., UserFilter) |
*RequestParameters builder classes |
| Execution | Explicit .execute() call |
Implicit execution on method call |
V3 introduces SyncPagingIterable<T> for automatic pagination, replacing the manual Request<Page> pattern.
v2:
import com.auth0.json.mgmt.users.UsersPage;
import com.auth0.client.mgmt.filter.UserFilter;
Request<UsersPage> request = mgmt.users().list(new UserFilter().withPage(0, 50));
UsersPage page = request.execute().getBody();
for (User user : page.getItems()) {
System.out.println(user.getEmail());
}
// Manual pagination
while (page.getNext() != null) {
request = mgmt.users().list(new UserFilter().withPage(page.getNext(), 50));
page = request.execute().getBody();
for (User user : page.getItems()) {
System.out.println(user.getEmail());
}
}v3:
import com.auth0.client.mgmt.core.SyncPagingIterable;
import com.auth0.client.mgmt.types.UserResponseSchema;
import com.auth0.client.mgmt.types.ListUsersRequestParameters;
// Automatic iteration through all pages
SyncPagingIterable<UserResponseSchema> users = client.users().list(
ListUsersRequestParameters
.builder()
.perPage(50)
.build()
);
for (UserResponseSchema user : users) {
System.out.println(user.getEmail());
}
// Or manual page control
List<UserResponseSchema> pageItems = users.getItems();
while (users.hasNext()) {
pageItems = users.nextPage().getItems();
// process page
}V3 uses a unified ManagementApiException class instead of the v2 exception hierarchy.
v2:
import com.auth0.exception.Auth0Exception;
import com.auth0.exception.APIException;
import com.auth0.exception.RateLimitException;
try {
User user = mgmt.users().get("user_id", null).execute().getBody();
} catch (RateLimitException e) {
// Rate limited
long retryAfter = e.getLimit();
} catch (APIException e) {
int statusCode = e.getStatusCode();
String error = e.getError();
String description = e.getDescription();
} catch (Auth0Exception e) {
// Network or other errors
}v3:
import com.auth0.client.mgmt.core.ManagementApiException;
try {
GetUserResponseContent user = client.users().get("user_id");
} catch (ManagementApiException e) {
int statusCode = e.statusCode();
Object body = e.body();
Map<String, List<String>> headers = e.headers();
String message = e.getMessage();
}V3 provides access to full HTTP response metadata via withRawResponse().
v2:
// Response wrapper provided status code
Response<User> response = mgmt.users().get("user_id", null).execute();
int statusCode = response.getStatusCode();
User user = response.getBody();v3:
import com.auth0.client.mgmt.core.ManagementApiHttpResponse;
// Use withRawResponse() to access headers and metadata
ManagementApiHttpResponse<GetUserResponseContent> response = client.users()
.withRawResponse()
.get("user_id");
GetUserResponseContent user = response.body();
Map<String, List<String>> headers = response.headers();V3 allows per-request configuration through RequestOptions.
v2:
// Most configuration was at client level only
// Request-level headers required creating a new request manually
Request<User> request = mgmt.users().get("user_id", null);
request.addHeader("X-Custom-Header", "value");
User user = request.execute().getBody();v3:
import com.auth0.client.mgmt.core.RequestOptions;
GetUserResponseContent user = client.users().get(
"user_id",
GetUserRequestParameters.builder().build(),
RequestOptions.builder()
.timeout(10)
.maxRetries(1)
.addHeader("X-Custom-Header", "value")
.build()
);V3 uses generated type classes located in com.auth0.client.mgmt.types instead of the hand-written POJOs in com.auth0.json.mgmt.
v2:
import com.auth0.json.mgmt.users.User;
import com.auth0.json.mgmt.roles.Role;
import com.auth0.json.mgmt.organizations.Organization;v3:
import com.auth0.client.mgmt.types.UserResponseSchema;
import com.auth0.client.mgmt.types.CreateUserRequestContent;
import com.auth0.client.mgmt.types.CreateUserResponseContent;
import com.auth0.client.mgmt.types.Role;
import com.auth0.client.mgmt.types.Organization;Type naming conventions in v3:
- Request body types:
*RequestContent(e.g.,CreateUserRequestContent) - Response types:
*ResponseContentor*ResponseSchema(e.g.,GetUserResponseContent,UserResponseSchema) - Query parameters:
*RequestParameters(e.g.,ListUsersRequestParameters)
All types use immutable builders:
// v3 type construction
CreateUserRequestContent request = CreateUserRequestContent
.builder()
.email("test@example.com")
.connection("Username-Password-Authentication")
.password("secure-password")
.build();