Skip navigation
All Places > Blackboard Developer Community > Blackboard Learn Developers > Blog

If you're attempting to build an LTI tool provider that supports Deep Linking aka Content Item Messaging and you see the following symptom described in Behind the Blackboard!


"LTI TP posts back to /webapps/blackboard/controller/lti/contentitem fails. When posting back to /webapps/blackboard/controller/lti/contentitem Learn fails with "The specified resource was not found, or you do not have permission to access it."


You can fix the issue by addressing two items in your code:

a) Blackboard Learn sends an optional "data" field with the LTI launch. Be certain you pass it back to Blackboard Learn.

b) The ContentItemSelection request sent to BB must be signed. You can check your signature with a tool like:

c) When checking the signature of the content received at your tool provider, unescape the content then create the signature to check. Do not sign the content before it has been unescaped. I.E. if you try to check the signature of content containing " the signature will not match because Learn signed it when that character was represented as ".  The same holds in the other direction. Learn expects the content to be signed before the Tool Provider escapes the HTML. To be clear - we're talking about this type of HTML Escape/Unescape - Free Online HTML Escape / Unescape Tool -  Also, when you want to send an empty value to Learn, avoid null. Use "" instead.


Lastly, after calculating the signature, be certain to escape the content posted back to Learn, not encode it. As an example, for the " character use " not %22.


The Partner that originally reported the issue got back to us and said they did the following above two items to their Tool Provider code to resolve it. In the meantime our automated processes created a known-issue article, then when the bug was closed by our LTI engineer as unable to reproduce it was set as being resolved in 3400.1.0. It's not a bug and functions as designed in all versions of Learn that content-item messaging is available.


Note: We've certified Blackboard Learn as an LTI Tool Consumer. If you have issues with your Tool Provider code and Blackboard Learn, our engineering team requests that you first certify your Tool Provider using the Learning Tools Interoperability Certification Suite | IMS Global Learning Consortium  - before bringing the issue to Blackboard.

tl;dr configuring REST in Learn and why doesn't my REST APP authorize with a Learn system that has a self-signed cert? Here's a video with a few pointers. Thanks for watching! Dropbox - RegardingSSLcertsAndRestIntegrationTool.mp4

I'm writing this to share how you can determine whether a given REST API is available in a particular Learn Release. I'm sharing this because we've had several queries, this should make it clear. If not, please do provide feed back in the comments section.


We'll use POST /learn/api/public/v1/courses/{courseId}/gradebook/columns as an example as this is a REST API one partner asked about.


Start by looking at and click the Explore button on the page. Look through the APIs and find course grades. Expand the API of interest by clicking on it.  In this case, POST /learn/api/public/v1/courses/{courseId}/gradebook/columns Notice Since: 3000.7.0 This is the build number that the POST REST API for gradebook columns was released. IMPORTANT: We never back port REST APIs. A cumulative update will not include a REST API release in a later build number. Example: 3000.0.9 will not include the POST gradebook/columns API.


Next, check this community post which explains how one can map recent Learn Releases (Q4 2016, ... etc.) to Build Numbers (3100.0.3, ... etc.).


The Build Number is what determines whether a REST API is available. The REST APIs are never backported via a cumulative update. We see that 3000.1.X is the build number for all Q2 2016 releases. The POST REST API for gradebook is not available there. The build number for all Q4 2016 releases and up is 3100 and greater. The POST REST API for grade book is available for all of these as we saw that is is available since 3000.7.0.


In conclusion, you should now be able to find out when any REST API became available for any QN YYYY release of Learn. For example, can you figure out which Learn releases now have the GET /learn/api/public/v1/courses/{courseId}/gradebook/columns/{columnId}/attempts API?


Happy New Year!




We've had many developers run into issues around site quotas and rate limits when moving their REST application into production use. To help smooth your transition to production we've published this document Developer Groups, Site Quotas, and Rate Limits  Please read it and give us your comments/questions. I won't say more here, the document should speak for itself! 

I'm writing this to show an easy way to test your REST API calls. This brief video shows you how to go about it. In the video we also answer a question about the Gradebook REST APIs. The question was whether the letter grade shows up in the REST call results when the letter grade is secondary. Watch to the video to find out, and learn how you can quickly troubleshoot REST API calls!


Update: We brought this to the attention of the development team with JIRA ticket API-1084. They've been clear in stating this is functioning as designed. Should you require different behavior, bring it to our attention on

This came up as a question from a developer, "How do I create a course in Learn that has a different course ID than the external Id? (The external Id is also often referred to as the batch UID.) The answer is to use the SIS Flat-File integration capability of Learn that is available on the System Administration tab. You can log in as a Learn administrator on a Learn system and follow along with the example given in this video: Create and Upload Courses Using a Flat File


For reference the documentation is given here:

Student Information System (SIS) | Blackboard Help

Create and Edit SIS Integrations | Blackboard Help

SIS Feed Files | Blackboard Help

Snapshot Flat File Examples | Blackboard Help

Course Examples | Blackboard Help

Hi All,


This topic comes up often regarding Learn versions, "How do I map the Learn release names to the build numbers?" One of our clients referred to finding the answer as "finding the secret decoder ring, or discovering the location of the Rosetta Stone." I'm writing this Blog post to share how I go about finding the answer.


It's simple. Just log in to Behind the Blackboard and use the search tool to look for the most recent release. I just entered "Q2 2017 CU4." There is no such release yet, but Cumulative Update 3 for Blackboard Learn, 9.1 Q2 2017 came up. Now scroll down the page and you will see the Upgrade Paths listed, which also maps the Release Names to the Build Numbers as shown below. And there you have it, your very own Secret Decoder Ring mapping of Learn Release Names to Build Numbers.




Posted by mkauffman Aug 4, 2017

This recently came up as a question from a partner, "In SaaS, can a external program call a JSP in the NOSESSION directory and thereby bypass Bb authentication?  This was possible in self and managed hosting of Learn."


I posted the question to our subject matter experts and got several responses that are quite helpful:

  1. You can just add authentication=“N” as an attribute to the <…Page> tag. Obviously, not recommended, nor ideal. Calling an endpoint remotely should have some sort of protection, even if it’s not via Learn. JSP seems like the worst way to accomplish this.
  2. Other than error pages, the only valid purpose I can see for requests NOT using a BbSession is a server/app/db liveness check, and we already have the /webapps/portal/healthCheck servlet for that purpose (which is a nosession servlet, and the only one we have in the app other than error pages). You really don't want to create a session for such requests that should merely check whether the server is up, but you also really shouldn't bombard the server with a multitude of such checks from different tools, or you're bound to introduce stability issues.
  3. There are other valid reasons I can think of.. The scorm AICC endpoint, the webservice endpoints etc all have their own authentication methods that don’t want a learn session. But I agree that they should be very careful about an unauthenticated endpoint.


Finally, when I mention JSPs being used as endpoints to our architects they all say that it far better to code using an MVC architecture where the endpoints are Java classes* (i.e. a method in a Spring controller) and JSPs are only used for the views. Both the JSP Model 1 and Model 2 architectures described here separate out logic from presentation.


*Not Java classes that are the result of the JSPs being compiled to Java.


Precompiling JSPs

Posted by scott.hurrey Jun 9, 2017

If you haven't read the Tomcat 8 section of the Preparing Your Building Blocks For Learn SaaS and Newer Learn Versions document, you should. As Blackboard continues to update to newer technologies, we are often looking at it as an opportunity to improve the product from a security, performance, and code quality perspective. This upgrade was certainly no different. As a matter of fact, the changes involved in Tomcat 8 to the way Building Blocks are handled is pretty substantial. While not a huge effort on your part is required to take advantage, some refactoring is required.


One of the biggest changes is that all Java Server Pages (JSPs) in your Building Block are expected to be precompiled. Its not a terribly difficult thing to do, but it does require an additional step in your build process. Historically -- and even currently if I'm honest -- Tomcat has handled the compilation on-demand; that is to say, the first time a user accesses a JSP, all of the JSPs are compiled. Not a huge deal, particularly if there are only a few to go through, but there is always the chance of a runtime compilation error and even if not, a busy system with a large number of compilations can be quite inconvenient to a student trying to get some work done.


So, as I set out to update the Building Blocks documentation, I realized I had never actually compiled a JSP myself. As you are probably aware, I am a Gradle user, and so I assumed someone out there must have tackled this problem before. I was right, sort of, but not nearly as many as I'd anticipated. Because of that, and because using the Blackboard Tag Libraries without having the tld and jar file in the WEB-INF directory introduced a complexity I could not find a solution to, I thought I would share what I did so you can think about how you might approach this.


Enter Benjamin Muschko.


Benjamin has done quite a bit of work around Gradle plugins, some of which I have used before, so I wasn't too surprised to see he had built the gradle-tomcat-plugin. There are others out there, but since I was familiar with the author, I decided to use this one. Setting it up and installing it is pretty trivial and pretty well documented in the README for the project. Essentially you must create a build script at the top of the build.gradle file to set up the classpath with a link to the plugin so you can apply it in your build file. Here is what that looks like:


buildscript {
    repositories {

    dependencies {
        classpath 'com.bmuschko:gradle-tomcat-plugin:2.2.5'

apply plugin: 'com.bmuschko.tomcat'
apply plugin: 'eclipse'
apply plugin: 'java'


Pretty straight forward, really. I should point out that I am using Gradle 3.3. If you are using an older version, upgrade. But if you don't want to, know that some of the syntax is different, and specifically, jcenter() is not a valid identifier, so you will need to manually reference as a maven repository, similar to how you declare the Blackboard maven repository. There are a millions google results to help you achieve that goal.


So I did this and the plugin was installed, and everything was great. The next step is to configure the plugin to use the appropriate version of Tomcat. This is extra important, because this plugin actually allows you to spin up a Tomcat server and run your code directly from Gradle. I'm not using it, but you definitely can. This also sets up your environment for compiling your JSPs.


Here is what that looks like according to the README :


// define the project's dependencies
dependencies {
   def tomcatVersion = '8.0.42'
    tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
    providedCompile "javax.servlet:servlet-api:2.5"

  // Dependencies are libraries needed to build, but should not be included in the B2 WAR.
  // You should NEVER include Learn JARs (other than webapis) in your B2.
  providedCompile( "blackboard.platform:bb-platform:$project.ext.learnVersion" ) { transitive = false }
  providedCompile( "blackboard.platform:bb-taglibs:$project.ext.learnVersion" ) { transitive = false }


So this was great. I did some optional configuration to turn of TLD verification and blam. Off to the races. Well, so I thought...


I ran the gradle command to compile the JSPs: gradle tomcatJasper, and after a few seconds, I received an error:

$ gradle tJ

:compileJava UP-TO-DATE
:tomcatJasper FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':tomcatJasper'.
> org.apache.jasper.JasperException: Unable to find taglib "bbNG" for URI: /bbNG


I did some noodling and playing and testing, and I figured that the problem must be that the classpath of the project wasn't being used as the classpath for the JSP compilation. I googled and yahooed and binged and I couldn't find any clear answer, so I did what I always do in this situation. I cried. Well, ok, I didn't, but I wanted to! Instead, I got a fresh cup of coffee and started reading the plugin code. In TomcatRun task, I came across a comment that said: "The web application's classpath. This classpath usually contains the external libraries assigned to the compile and runtime configurations."


I then looked at what the code was doing to set up the directory that the compilation was taking place, and it was essentially copying the WEB-INF folder. Because we use provideCompile to add the taglibs to the classpath but explicitly not include the jar file in the WEB-INF directory, there was no TLD to be found.


To test, I changed the dependency to compile and re-ran the test. I got a different message this time. AH HA!!


Of course, I can't leave the taglib in the Building Block, and I'm too lazy to change it back and forth to compile the JSP and then the WAR file, I had essentially confirmed the cause, but was still no closer to figuring out the solution. Again, I set my fingers in web crawl mode and hunkered down to search for ideas. After a couple of hours, my head hurt from banging it on the desk and my fingers were tired, and I still had no solution.


I took a step back and just looked through the build.gradle file to see what was there. I had tried adding additionalRuntimeResources, but that only works for TomcatRun and TomcatRunWar. I had tried creating my own classpath and adding the taglibs to the Gradle class path, and nothing. Then I noticed the tomcat dependency, and specifically how it was adding in a few libraries specifically to configure the plugin to setup the correct Tomcat version. I thought, what the heck, lets add the taglib there. I ran the task, and wouldn't you know it! I got the same error I got when I used compile instead of providedCompile. The error referenced the fact that it could not find one of the Blackboard objects, so i said, what the heck, let's add the platform jar, too, and low and behold, I got an error about the JSTL fmt tag library. I added that one, too, and I got those two words every Gradle user loves: BUILD SUCCESSFUL.


Here's what that looked like:


// define the project's dependencies
dependencies {
   def tomcatVersion = '8.0.42'
    tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",

  providedCompile "javax.servlet:servlet-api:2.5"

  // Dependencies are libraries needed to build, but should not be included in the B2 WAR.
  // You should NEVER include Learn JARs (other than webapis) in your B2.
  provompile( "blackboard.platform:bb-platform:$project.ext.learnVersion" ) { transitive = false }
  providedCompile( "blackboard.platform:bb-taglibs:$project.ext.learnVersion" ) { transitive = false }


So after all of that, I finally had Gradle compiling my JSPs. Since I already survived the struggle, I thought I would share here with you!


I am very overdue in updating the basic-b2-template project, so I am doing that now, to include the precompilation and any other updates that are missing since Gradle 1.6 was cool. I will update the group when it is complete.


happy Developing!!


When Blackboard migrates a self or managed-hosted system to SaaS, the migration team and client sometimes choose to do a full database migration in order to maintain existing content. Your B2's database tables will be migrated by this process, but if your B2 code relies on any database sequences, you'll need to work with the migration team to ensure that the sequences are correctly set up on the new SaaS system as well.


Bye Bye BBLEARN & bb_bb60

Posted by mkauffman May 14, 2017

Please read carefully. Your integration will break in SaaS if you have issues caused by hard-coded use of BBLEARN and bb_bb60. Though the title is a bit-overdramatic and BBLEARN and bb_bb60 will still be around on self-hosted and managed-hosted systems, they are gone in SaaS.


BBLEARN are bb_bb60 are the schema names used on many self and managed-hosted systems. These are no longer used on SaaS systems. Instead a long string like BB589bd9cca452e is used as the schema name. Every SaaS system will have a different schema name for its database. This means that no external system can make assumptions about the schema name and no B2 code can count on some particular set of values. All code must dynamically determine the schema name, or at the very least it must be set up during configuration. The Learn Java API blackboard.platfomr.plugin.PlugInUtil.getUriStem method can be used to get the complete B2 web application name, including the schema name. For example, on, String uriStem = PlugInUtil.getUriStem("bbdn", "bblogbackb2"); currently returns the string: /webapps/bbdn-bblogbackb2-BB56d7008520956/ See GitHub - mark-b-kauffman/bbdn-bblogbackb2: Demo the use of Logback to create log files.   for example use code.


Should your B2 be exposing Web Service endpoints and your external server assumes the endpoint is either BBLEARN or bb_bb60, the fact that SaaS now uses a different schema name on every SaaS system will cause problems for your integration. You external server must now be given either programmatically, or during configuration, the actual schema name so that it can make calls to valid URIs for each Learn system.  The more dynamic you make your code, the better.


The other case that will cause issues is if your B2 code refers to itself on the Learn system using URIs that it assumes contain either bb_bb60 or BBLEARN. The code must no longer make that assumption. Instead use PlugInUtil.getUriStem() to determine the actual value.


A brief video explanation - 2017-05-18_1526

Please read ALL FILES No More

For self-hosted Unix installations see How much longer will Red Hat 5 be supported?


Blackboard Learn Mobile

Posted by mkauffman Mar 7, 2017

We've had several developers ask about registering Mobile Web Services. The first step is to read this article on how to Configure and Register Blackboard Mobile Web Services. After reading, the common roadblock is filling out the registration form, hitting submit, and then the page reloads with the default values, like nothing happened. What's wrong? The answer is that the server must be on the public internet and have a valid SSL certificate installed. This has to be because the Mobile service is a cloud service that talks back to your Learn instance.

This just in. bb-manifest.xml files will not upload to SaaS when the XML does not meet the standard: Extensible Markup Language (XML) 1.0 (Second Edition) .As we don't check this in Self and Managed-hosted systems, you may have had URLs in your bb-manifest.xml file using the following format:

<url value="itemanalysis/showItemAnalysis?action=display&;course_id=@X@course.pk_string@X@" />


These should be written as follows because the & character in XML must be encoded:

<url value="itemanalysis/showItemAnalysis?action=display&amp;course_id=@X@course.pk_string@X@" />