Custom REST API -- Strange behavior

Which IIQ version are you inquiring about?

IdentityIQ 8.4

I am writing a set of custom REST APIs for IdentityIQ 8.4 to expose some functionality to other developers. I am seeing some very strange behavior in even the most basic use case, though. Here’s what I have…

I have my custom REST Application object…

public class CustomRestApplication extends SailPointRestApplication {

    public CustomRestApplication() {
        super();
        register(TestRestService.class);
    }
}

I am able to register my REST API endpoint with no problem, as shown. Here’s what the code for that endpoint looks like…

@Path("/demo")
public class TestRestService {

    private static final Logger logger = LoggerFactory.getLogger(TestRestService.class);

    @Path("/test")
    @Produces("application/json")
    public Response runTest() {
        if (logger.isDebugEnabled()) {
            logger.debug("runTest() - start");
        }

        TestObjectModel obj = new TestObjectModel();
        obj.setBar("BAR : " + OffsetDateTime.now());
        obj.setFoo("FOO : " + OffsetDateTime.now());

        if (logger.isDebugEnabled()) {
            logger.debug("runTest() - end");
        }
        return Response.ok(obj).build();
    }
}

As you see, there’s not much to this. I have updated the web.xml file accordingly to reference my custom REST Application…


    <servlet>

      <servlet-name>JAX-RS REST Servlet</servlet-name>

<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>




<!-- 

        Ideally, we would let Jersey scan for annotated classes, but this causes

        some application servers grief.  Instead we'll hardcode these in our 

        custom Application for now.

       -->

<init-param>

<param-name>javax.ws.rs.Application</param-name>

<param-value>com.myproject.iam.sailpoint.jaxrs.CustomRestApplication</param-value>

</init-param>

<!-- <init-param>

        <param-name>javax.ws.rs.Application</param-name>

        <param-value>sailpoint.rest.SailPointRestApplication</param-value>

      </init-param> -->




<!--

        Disable WADL generation by default

       -->

<init-param>

<param-name>jersey.config.server.wadl.disableWadl</param-name>

<param-value>true</param-value>

</init-param>

</servlet>


I do not receive any errors at startup, and everything seems to be configured properly. When I go to call this endpoint, however, I get a 404. The VERY weird part, though, is that I see logging from my REST API endpoint. So, the call is obviously going through. My code is being executed. However, my result is not being returned. Instead, I get a 404.

Here is the curl command for my call…

curl -vki -u "spadmin:admin" http://localhost:8080/identityiq/rest/trp/demo/test

* Uses proxy env variable no_proxy == 'localhost,127.0.0.1,*.local'

* Host localhost:8080 was resolved.

* IPv6: ::1

* IPv4: 127.0.0.1

*   Trying [::1]:8080...

* Connected to localhost (::1) port 8080

* Server auth using Basic with user 'spadmin'

> GET /identityiq/rest/demo/test HTTP/1.1

> Host: localhost:8080

> Authorization: Basic c3BhZG1pbjphZG1pbg==

> User-Agent: curl/8.7.1

> Accept: */*

>

* Request completely sent off

< HTTP/1.1 404

HTTP/1.1 404

< Cache-Control: no-store

Cache-Control: no-store

< Pragma: no-cache

Pragma: no-cache

< Set-Cookie: JSESSIONID=C9A849771B322A6C8F13E9B3BF05AF9C; Path=/identityiq; HttpOnly

Set-Cookie: JSESSIONID=C9A849771B322A6C8F13E9B3BF05AF9C; Path=/identityiq; HttpOnly

< Content-Type: text/html;charset=UTF-8

Content-Type: text/html;charset=UTF-8

< Content-Length: 706

Content-Length: 706

< Date: Tue, 25 Nov 2025 15:56:25 GMT

Date: Tue, 25 Nov 2025 15:56:25 GMT

<



<!-- (c) Copyright 2008 SailPoint Technologies, Inc., All Rights Reserved. -->



<html>

  <head>

    <title>Page Not Found</title>

  </head>



<body>

<!--

  Need to add some padding.  If this is less the 512 bytes, IE will not display it but

  instead display the friendly HTTP error page.  This is because the errorthreshold as

  configured in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Main\ErrorThresholds

  defaults to 512 bytes.  This is a particularly a problem on WebSphere because it generates

  very small 404 error responses.  This only happens when "Show friendly HTTP errors" is

  enabled in IE.

  -->



<h1>File Not Found - 404 Error</h1>



The requested file was not found.



</body>

* Connection #0 to host localhost left intact


And here is what I see in the logs, showing that my call does actually go through!

2025-11-25T10:56:24,938 DEBUG http-nio-8080-exec-2 sailpoint.jaxrs.api.TestRestService:21 - runTest() - start

2025-11-25T10:56:24,944 DEBUG http-nio-8080-exec-2 sailpoint.jaxrs.api.TestRestService:29 - runTest() - end


Any idea what I could be missing here? Have I missed a configuration step? I could understand a 404 if my path was incorrect or if the service wasn’t registered. But that clearly isn’t the case, considering I see logging statements from my code. Thoughts?

Hi @sdnakhla I think nothing wrong with IIQ here, right now your method has a path but doesn’t have HTTP method/verb, could you please add @Get before the path line for the method runTest and check again…

Please don’t hesitate to let me know how it went, and glad to further troubleshoot/debug with you :slight_smile:

Regards,

Mustafa

that should return 200


@Path("/demo")
public class TestRestService {

    private static final Logger logger = LoggerFactory.getLogger(TestRestService.class);

    @GET
    @Path("/test")
    @Produces(MediaType.APPLICATION_JSON)
    public Response runTest() {
        if (logger.isDebugEnabled()) {
            logger.debug("runTest() - start");
        }

        TestObjectModel obj = new TestObjectModel();
        obj.setBar("BAR : " + OffsetDateTime.now());
        obj.setFoo("FOO : " + OffsetDateTime.now());

        if (logger.isDebugEnabled()) {
            logger.debug("runTest() - end");
        }

        return Response.ok(obj).build();
    }
}

With that in place, your curl to: “http://localhost:8080/identityiq/rest/demo/test“

I have written hundreds of JAX-RS services. I can’t believe I missed something so dumb! That was definitely the issue. Thanks for the extra set of eyes!

Actually it happens to all of us :slight_smile: , especially with JAX-RS quirks. Happy it helped, and you are more than welcome anytime :+1:

Have a nice and great one!

I’m curious. Why extend SailPointRestApplication instead of extending BasePluginResource? With the latter, you get:

  • Authentication checks implemented for free
  • Annotation-driven configuration for SPRight-based authorization checks (@RequiredRight , @AllowAll, @SystemAdmin, @Deferred)
  • Easy access to a SailPointContext
  • Easy deployment and hot-redeployment
  • Better encapsulation

Good point @kjperkin for a proper design on 8.4, I totally agree… exposing these as plugin REST endpoints is the better long-term approach.

I will investigate the plugin approach. One area of concern for us is around authentication. We do not want to use the IdentityIQ-generated OAuth tokens. Instead, we want to use OAuth tokens generated by our existing OAuth provider. Is that an option? Or, at the very least, can I override the default behavior and write a custom authenticator?

Ah, yeah. If you need custom authentication (not just authorization), I’m not sure if that is doable with the plugin framework… I’m leaning towards “no”.

Hi @sdnakhla, based on my understanding and as far as I know the Plugin REST tied into the standard IIQ auth stack, and there isn’t really a clean, supported way to swap that out and make a third-party OAuth provider the “native” authenticator on the endpoint itself.

What logically can be done “however I didn’t try them before - so it’s important please validate again”:

  1. Put an API gateway / reverse proxy in front of IIQ that validates your existing OAuth tokens, then have it call IIQ using one of the supported methods (Basic or IIQ OAuth).

  2. If you really want to keep everything inside IIQ, you can mark a plugin resource as @AllowAll and do your own token validation + identity mapping + authorization in code, but that becomes a fully custom pattern that you’d own from a security and maintenance perspective.

I hope that could help :), have a nice and great one!

Regards,

Mustafa