diff --git a/ovsdb/monitor.h b/ovsdb/monitor.h
index b1a5146..d6e9635 100644
--- a/ovsdb/monitor.h
+++ b/ovsdb/monitor.h
@@ -18,6 +18,7 @@
 #define OVSDB_MONITOR_H
 
 struct ovsdb_monitor;
+struct ovsdb_jsonrpc_monitor;
 
 enum ovsdb_monitor_selection {
     OJMS_INITIAL = 1 << 0,      /* All rows when monitor is created. */
diff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in
index fbb7148..422f6e1 100644
--- a/ovsdb/ovsdb-client.1.in
+++ b/ovsdb/ovsdb-client.1.in
@@ -32,6 +32,11 @@ ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1)
 .br
 \fBovsdb\-client \fR[\fIoptions\fR] \fBmonitor\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fBALL\fR
 .br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBmonitor2\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fItable\fR
+[\fIcolumn\fR[\fB,\fIcolumn\fR]...]...
+.br
+\fBovsdb\-client \fR[\fIoptions\fR] \fBmonitor2\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fBALL\fR
+.br
 \fBovsdb\-client help\fR
 .IP "Output formatting options:"
 [\fB\-\-format=\fIformat\fR]
@@ -105,6 +110,7 @@ Connects to \fIserver\fR, retrieves all of the data in \fIdatabase\fR,
 and prints it on stdout as a series of tables.
 .
 .IP "\fBmonitor\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fItable\fR [\fIcolumn\fR[\fB,\fIcolumn\fR]...]..."
+.IQ "\fBmonitor2\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fItable\fR [\fIcolumn\fR[\fB,\fIcolumn\fR]...]..."
 Connects to \fIserver\fR and monitors the contents of \fItable\fR in
 \fIdatabase\fR.  By default, the initial contents of \fItable\fR are
 printed, followed by each change as it occurs.  If at least one
@@ -127,16 +133,25 @@ each group.  Whether multiple groups or only a single group is
 specified, any given column may only be mentioned once on the command
 line.
 .IP
-If \fB\-\-detach\fR is used with \fBmonitor\fR, then \fBovsdb\-client\fR
-detaches after it has successfully received and printed the initial
-contents of \fItable\fR.
+If \fB\-\-detach\fR is used with \fBmonitor\fR or \fBmointor2\fR, then
+\fBovsdb\-client\fR detaches after it has successfully received and
+printed the initial contents of \fItable\fR.
+.IP
+The \fBmonitor\fR command uses RFC 7047 "monitor" method to open a monitor
+session with the server. The \fBmonitor2\fR command uses RFC 7047
+extension "monitor2" method. See \fBovsdb\-server\fR(1) for details.
 .
 .IP "\fBmonitor\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fBALL\fR"
+.IQ "\fBmonitor2\fI \fR[\fIserver\fR] \fR[\fIdatabase\fR] \fBALL\fR"
 Connects to \fIserver\fR and monitors the contents of all tables in
 \fIdatabase\fR.  Prints initial values and all kinds of changes to all
 columns in the database.  The \fB\-\-detach\fR option causes
 \fBovsdb\-client\fR to detach after it successfully receives and
 prints the initial database contents.
+.IP
+The \fBmonitor\fR command uses RFC 7047 "monitor" method to open a monitor
+session with the server. The \fBmonitor2\fR command uses RFC 7047
+extension "monitor2" method. See \fBovsdb\-server\fR(1) for details.
 .
 .SH OPTIONS
 .SS "Output Formatting Options"
@@ -147,14 +162,14 @@ The following options controlling output formatting:
 .so lib/table.man
 .
 .IP "\fB\-\-timestamp\fR"
-For the \fBmonitor\fR command, adds a timestamp to each table
-update.  Most output formats add the timestamp on a line of its own
+For the \fBmonitor\fR and \fBmonitor2\fR commands, add a timestamp to each
+table update.  Most output formats add the timestamp on a line of its own
 just above the table.  The JSON output format puts the timestamp in a
 member of the top-level JSON object named \fBtime\fR.
 .
 .SS "Daemon Options"
-The daemon options apply only to the \fBmonitor\fR command.  With any
-other command, they have no effect.
+The daemon options apply only to the \fBmonitor\fR and \fBmonitor2\fR commands.
+With any other command, they have no effect.
 .ds DD
 .so lib/daemon.man
 .SS "Logging Options"
diff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c
index b59388f..d676f78 100644
--- a/ovsdb/ovsdb-client.c
+++ b/ovsdb/ovsdb-client.c
@@ -44,6 +44,7 @@
 #include "stream.h"
 #include "stream-ssl.h"
 #include "table.h"
+#include "monitor.h"
 #include "timeval.h"
 #include "unixctl.h"
 #include "util.h"
@@ -256,6 +257,9 @@ usage(void)
            "\n  monitor [SERVER] [DATABASE] ALL\n"
            "    monitor all changes to all columns in all tables\n"
            "    in DATBASE on SERVER.\n"
+           "\n  monitor2 [SERVER] [DATABASE] ALL\n"
+           "    same usage as monitor, but uses \"monitor2\" method over"
+           "    the wire."
            "\n  dump [SERVER] [DATABASE]\n"
            "    dump contents of DATABASE on SERVER to stdout\n"
            "\nThe default SERVER is unix:%s/db.sock.\n"
@@ -617,6 +621,101 @@ monitor_print(struct json *table_updates,
 }
 
 static void
+monitor2_print_row(struct json *row, const char *type, const char *uuid,
+                   const struct ovsdb_column_set *columns, struct table *t)
+{
+    if (!strcmp(type, "delete")) {
+        if (row->type != JSON_NULL) {
+            ovs_error(0, "delete method does not expect <row>");
+            return;
+        }
+
+        table_add_row(t);
+        table_add_cell(t)->text = xstrdup(uuid);
+        table_add_cell(t)->text = xstrdup(type);
+    } else {
+        if (!row || row->type != JSON_OBJECT) {
+            ovs_error(0, "<row> is not object");
+            return;
+        }
+        monitor_print_row(row, type, uuid, columns, t);
+    }
+}
+
+static void
+monitor2_print_table(struct json *table_update2,
+                    const struct monitored_table *mt, char *caption)
+{
+    const struct ovsdb_table_schema *table = mt->table;
+    const struct ovsdb_column_set *columns = &mt->columns;
+    struct shash_node *node;
+    struct table t;
+    size_t i;
+
+    if (table_update2->type != JSON_OBJECT) {
+        ovs_error(0, "<table-update> for table %s is not object", table->name);
+        return;
+    }
+
+    table_init(&t);
+    table_set_timestamp(&t, timestamp);
+    table_set_caption(&t, caption);
+
+    table_add_column(&t, "row");
+    table_add_column(&t, "action");
+    for (i = 0; i < columns->n_columns; i++) {
+        table_add_column(&t, "%s", columns->columns[i]->name);
+    }
+    SHASH_FOR_EACH (node, json_object(table_update2)) {
+        struct json *row_update2 = node->data;
+        const char *operation;
+        struct json *row;
+        const char *ops[] = {"delete", "initial", "modify", "insert"};
+
+        if (row_update2->type != JSON_OBJECT) {
+            ovs_error(0, "<row-update2> is not object");
+            continue;
+        }
+
+        /* row_update2 contains one of objects indexed by ops[] */
+        for (int i = 0; i < ARRAY_SIZE(ops); i++) {
+            operation = ops[i];
+            row = shash_find_data(json_object(row_update2), operation);
+
+            if (row) {
+                monitor2_print_row(row, operation, node->name, columns, &t);
+                break;
+            }
+        }
+    }
+    table_print(&t, &table_style);
+    table_destroy(&t);
+}
+
+static void
+monitor2_print(struct json *table_updates2,
+               const struct monitored_table *mts, size_t n_mts)
+{
+    size_t i;
+
+    if (table_updates2->type != JSON_OBJECT) {
+        ovs_error(0, "<table-updates2> is not object");
+        return;
+    }
+
+    for (i = 0; i < n_mts; i++) {
+        const struct monitored_table *mt = &mts[i];
+        struct json *table_update = shash_find_data(
+                                        json_object(table_updates2),
+                                        mt->table->name);
+        if (table_update) {
+            monitor2_print_table(table_update, mt,
+                                n_mts > 1 ? xstrdup(mt->table->name) : NULL);
+        }
+    }
+}
+
+static void
 add_column(const char *server, const struct ovsdb_column *column,
            struct ovsdb_column_set *columns, struct json *columns_json)
 {
@@ -776,8 +875,9 @@ add_monitored_table(int argc, char *argv[],
 }
 
 static void
-do_monitor(struct jsonrpc *rpc, const char *database,
-           int argc, char *argv[])
+do_monitor__(struct jsonrpc *rpc, const char *database,
+             enum ovsdb_monitor_version version,
+             int argc, char *argv[])
 {
     const char *server = jsonrpc_get_name(rpc);
     const char *table_name = argv[0];
@@ -791,6 +891,8 @@ do_monitor(struct jsonrpc *rpc, const char *database,
     struct monitored_table *mts;
     size_t n_mts, allocated_mts;
 
+    ovs_assert(version < OVSDB_MONITOR_VERSION_MAX);
+
     daemon_save_fd(STDOUT_FILENO);
     daemonize_start(false);
     if (get_detach()) {
@@ -845,7 +947,10 @@ do_monitor(struct jsonrpc *rpc, const char *database,
 
     monitor = json_array_create_3(json_string_create(database),
                                   json_null_create(), monitor_requests);
-    request = jsonrpc_create_request("monitor", monitor, NULL);
+    const char *method = version == OVSDB_MONITOR_V2 ? "monitor2"
+                                                     : "monitor";
+
+    request = jsonrpc_create_request(method, monitor, NULL);
     request_id = json_clone(request->id);
     jsonrpc_send(rpc, request);
 
@@ -867,7 +972,17 @@ do_monitor(struct jsonrpc *rpc, const char *database,
                                                        msg->id));
             } else if (msg->type == JSONRPC_REPLY
                        && json_equal(msg->id, request_id)) {
-                monitor_print(msg->result, mts, n_mts, true);
+                switch(version) {
+                case OVSDB_MONITOR_V1:
+                    monitor_print(msg->result, mts, n_mts, true);
+                    break;
+                case OVSDB_MONITOR_V2:
+                    monitor2_print(msg->result, mts, n_mts);
+                    break;
+                case OVSDB_MONITOR_VERSION_MAX:
+                default:
+                    OVS_NOT_REACHED();
+                }
                 fflush(stdout);
                 daemonize_complete();
             } else if (msg->type == JSONRPC_NOTIFY
@@ -879,6 +994,16 @@ do_monitor(struct jsonrpc *rpc, const char *database,
                     monitor_print(params->u.array.elems[1], mts, n_mts, false);
                     fflush(stdout);
                 }
+            } else if (msg->type == JSONRPC_NOTIFY
+                       && version == OVSDB_MONITOR_V2
+                       && !strcmp(msg->method, "update2")) {
+                struct json *params = msg->params;
+                if (params->type == JSON_ARRAY
+                    && params->u.array.n == 2
+                    && params->u.array.elems[0]->type == JSON_NULL) {
+                    monitor2_print(params->u.array.elems[1], mts, n_mts);
+                    fflush(stdout);
+                }
             }
             jsonrpc_msg_destroy(msg);
         }
@@ -897,6 +1022,20 @@ do_monitor(struct jsonrpc *rpc, const char *database,
     }
 }
 
+static void
+do_monitor(struct jsonrpc *rpc, const char *database,
+           int argc, char *argv[])
+{
+    do_monitor__(rpc, database, OVSDB_MONITOR_V1, argc, argv);
+}
+
+static void
+do_monitor2(struct jsonrpc *rpc, const char *database,
+           int argc, char *argv[])
+{
+    do_monitor__(rpc, database, OVSDB_MONITOR_V2, argc, argv);
+}
+
 struct dump_table_aux {
     struct ovsdb_datum **data;
     const struct ovsdb_column **columns;
@@ -1136,6 +1275,7 @@ static const struct ovsdb_client_command all_commands[] = {
     { "list-columns",       NEED_DATABASE, 0, 1,       do_list_columns },
     { "transact",           NEED_RPC,      1, 1,       do_transact },
     { "monitor",            NEED_DATABASE, 1, INT_MAX, do_monitor },
+    { "monitor2",           NEED_DATABASE, 1, INT_MAX, do_monitor2 },
     { "dump",               NEED_DATABASE, 0, 0,       do_dump },
 
     { "help",               NEED_NONE,     0, INT_MAX, do_help },
